---
title: "PDF.js accessibility, StructTree, presentation mode, and printing"
canonical_url: "https://www.nutrient.io/blog/pdfjs-accessibility-structtree-printing/"
md_url: "https://www.nutrient.io/blog/pdfjs-accessibility-structtree-printing.md"
last_updated: "2026-08-04T17:20:06.541Z"
description: "Implement StructTree accessibility for screen readers, fullscreen presentation mode, high-resolution printing, and custom page labels in a PDF.js viewer."
---

**TL;DR**

- Render PDF.js’s StructTree layer so screen readers can navigate tagged PDFs by headings, lists, and tables.

- Wire up `PDFPresentationMode` for fullscreen slide-deck playback.

- Render print canvases at 3× with `intent: "print"` and read custom page labels via `getPageLabels()`.

## Prerequisites

This guide assumes a PDF.js viewer composed from `pdfjs-dist` — `PDFViewer`, `EventBus`, and `PDFLinkService` — already set up, and a `pdfDocument` loaded via `getDocument()`. If you haven’t built that yet, start with our blog on [how to set up a custom PDF.js viewer in React](https://www.nutrient.io/blog/pdfjs-react-viewer-setup.md).

You’ll also need:

- `pdfjs-dist` 4.x or later

- A tagged PDF to test the StructTree section (most government and accessibility-audited PDFs qualify)

## StructTreeLayer: Accessibility for tagged PDFs

Tagged PDFs contain a structure tree — a semantic representation of the document (headings, paragraphs, lists, tables) that screen readers use. PDF.js can render this as an invisible DOM tree alongside the visible canvas.

### Enabling StructTree

The `PDFViewer` renders the struct tree automatically for tagged PDFs. To use it with custom page rendering:

```tsx

async function renderStructTree(page, viewport, container) {
  const structTree = await page.getStructTree();

  if (structTree) {
    // The struct tree can be used to build accessible DOM elements
    // that map to the visual content on the page.
    const treeLayer = document.createElement("div");
    treeLayer.className = "structTree";
    // `buildAccessibleTree` is your own helper — walk `structTree.children`
    // and emit semantic HTML (`h1`–`h6`, `p`, `ul`/`li`, `table`) with `aria-*` attributes.
    buildAccessibleTree(treeLayer, structTree);
    container.appendChild(treeLayer);
  }
}

```

### What tagged PDFs provide

`getMarkInfo()` reports whether a document was tagged for accessibility at all, before any struct tree work happens:

```tsx

const markInfo = await pdfDocument.getMarkInfo();
// `{ Marked: true, UserProperties: false, Suspects: false }`.

if (markInfo?.Marked) {
  console.log("This PDF has accessibility tags");
}

```

The struct tree maps semantic elements to their visual locations:

- Headings (H1–H6)

- Paragraphs

- Lists and list items

- Tables with rows and cells

- Figures with alt text

- Links

### Why it matters

Screen readers can navigate a tagged PDF by headings, read table structures, and describe images — but only if the struct tree layer is rendered. The `PDFViewer` handles this automatically.

## Presentation mode

PDF.js includes a built-in fullscreen presentation mode, which is useful for slide decks:

```tsx

const pdfjs = await import("pdfjs-dist/web/pdf_viewer.mjs");

const presentationMode = new pdfjs.PDFPresentationMode({
  container: document.getElementById("pdf-container"),
  pdfViewer: viewer,
  eventBus,
});

// Enter presentation mode.
presentationMode.request();

```

### Custom presentation controls

The default presentation mode has no slide navigation UI, so keyboard controls and state tracking need to be wired up manually:

```tsx

// Listen for presentation mode changes.
eventBus.on("presentationmodechanged", (evt) => {
  // `evt.state` is a `PresentationModeState` enum value:
  // `UNKNOWN` (0), `NORMAL` (1), `CHANGING` (2), `FULLSCREEN` (3).
  console.log(evt.state);
});

// Navigate in presentation mode.
document.addEventListener("keydown", (e) => {
  if (e.key === "ArrowRight" || e.key === " ") {
    viewer.nextPage();
  } else if (e.key === "ArrowLeft") {
    viewer.previousPage();
  } else if (e.key === "Escape") {
    // Exit is handled automatically.
  }
});

```

### CSS for presentation mode

Presentation mode adds a `pdfPresentationMode` class to the container, which can be targeted to hide toolbars and style the fullscreen background:

```css

/* Hide UI elements in presentation mode */.pdfPresentationMode.pdf-toolbar,.pdfPresentationMode.sidebar {
  display: none!important;
}.pdfPresentationMode #pdf-container {

  background: black;
}.pdfPresentationMode.page {
  margin: 0 auto;
}

```

## Printing

PDF.js includes a print service that renders pages at high resolution for printing.

### Using the built-in print service

PDF.js can hand off printing to the browser’s own print dialog through either the `EventBus` or the standard `window.print()` call:

```tsx

// Trigger print via `EventBus`.
eventBus.dispatch("print", { source: window });

// Or use the window print with PDF.js preparation.
window.print();

```

### Custom print implementation

For more control, render pages to high-resolution canvases:

```tsx

async function printPdf(pdfDocument) {
  const printContainer = document.createElement("div");
  printContainer.className = "printContainer";
  document.body.appendChild(printContainer);

  for (let i = 1; i <= pdfDocument.numPages; i++) {
    const page = await pdfDocument.getPage(i);
    const viewport = page.getViewport({ scale: 3 }); // High-res for print.

    const canvas = document.createElement("canvas");
    canvas.width = viewport.width;
    canvas.height = viewport.height;

    await page.render({
      canvasContext: canvas.getContext("2d"),
      viewport,
      intent: "print", // Optimizes rendering for print.
    }).promise;

    const pageDiv = document.createElement("div");
    pageDiv.className = "printPage";
    pageDiv.appendChild(canvas);
    printContainer.appendChild(pageDiv);
  }

  window.print();
  document.body.removeChild(printContainer);
}

```

### Print CSS

The custom print implementation needs `@media print` rules to hide everything on the page except the generated print container:

```css

@media print {
  body > *:not(.printContainer) {
    display: none!important;
  }.printContainer {
    display: block!important;
  }.printPage {
    page-break-after: always;
  }.printPage canvas {
    width: 100%;
    height: auto;
  }
}

```

### The intent parameter

When rendering for print, pass `intent: "print"`:

```tsx

await page.render({
  canvasContext: context,
  viewport,
  intent: "print", // vs `"display"` (default).
}).promise;

```

This tells PDF.js to:

- Use higher-quality rendering paths

- Include print-only annotations

- Skip display-only annotations

## Page labels

Some PDFs use custom page labels (roman numerals, letters, etc.):

```tsx

const labels = await pdfDocument.getPageLabels();
// ["i", "ii", "iii", "iv", "1", "2", "3",...]
// or `null` if no custom labels.

if (labels) {
  // Use `labels[pageIndex]` instead of page numbers in your UI.
  const label = labels[currentPage - 1]; // "iii" instead of "3".
}

```

## JavaScript actions

Some PDFs contain embedded JavaScript (for form validation, auto-calculations):

```tsx

const jsActions = await pdfDocument.getJSActions();
// `{ OpenAction: ["app.alert('Welcome')"],... }`.

```

**Security note:** Be very cautious about executing PDF JavaScript. Most viewers sandbox or ignore it entirely.

## Key points

- Tagged PDFs expose a struct tree for screen reader accessibility — `PDFViewer` renders it automatically.

- Check `getMarkInfo()` to determine if a PDF has accessibility tags.

- `PDFPresentationMode` provides fullscreen slideshow mode for slide decks.

- Use `intent: "print"` when rendering for print to get optimized quality.

- `getPageLabels()` returns custom page labels if the PDF defines them.

- These are all built into PDF.js — no extra dependencies or configuration needed.

## How Nutrient Web SDK handles this

Nutrient Web SDK handles StructTree rendering, print preparation, and the `intent` parameter internally. Accessibility tagging, screen reader support, Accessible Rich Internet Applications (ARIA) labels, and keyboard navigation are wired up by the viewer; printing is one method call.

```js

// Accessibility is built into the viewer — no manual StructTree rendering required.
// Keyboard navigation, screen reader support, and ARIA labels are wired up
// to support WCAG 2.1 AA accessibility goals.

// Print at high resolution with a single call.
instance.print();

```

That replaces the custom StructTree pipeline, the print container scaffolding, and the per-render `intent: "print"` flag with a single configured viewer.

## FAQ

#### Does PDF.js print at full resolution by default?

No. The default render `intent` is `"display"`, which is optimized for onscreen quality, not paper. For sharp print output, render to an offscreen canvas with `getViewport({ scale: 3 })` (or higher) and pass `intent: "print"` to `page.render()`. Then append those canvases to a print container and call `window.print()`.

#### What does <code>intent: "print"</code> actually change?

It tells PDF.js to use rendering paths tuned for print output, include print-only annotations (annotations whose flags mark them as printable), and skip display-only annotations. It doesn’t change the canvas resolution — you still need to bump `scale` on the viewport for high-DPI output.

#### How do I tell if a PDF is accessible (tagged)?

Call `pdfDocument.getMarkInfo()`. It returns `{ Marked, UserProperties, Suspects }`. If `Marked` is `true`, the PDF has a structure tree that screen readers can navigate. You can then call `page.getStructTree()` to walk the semantic hierarchy (headings, paragraphs, lists, tables).

#### Do I have to manually render the StructTree?

Only if you’re rendering pages yourself with `page.render()`. The full `PDFViewer` component from `pdfjs-dist/web/pdf_viewer.mjs` renders the struct tree layer automatically alongside the canvas, so screen readers can navigate without extra code.

#### Is it safe to execute JavaScript embedded in a PDF?

No. PDF JavaScript can include form-validation logic and auto-calculations, but it can also include hostile code if the PDF is untrusted. `pdfDocument.getJSActions()` lets you read what scripts exist, but most viewers (including PDF.js’s default UI) sandbox or ignore them. Don’t `eval()` PDF script strings in your app.

#### How do I show roman-numeral page labels (i, ii, iii) instead of page numbers?

Call `pdfDocument.getPageLabels()`. It returns an array like `["i", "ii", "iii", "1", "2", "3",...]` or `null` if the PDF defines no custom labels. Use `labels[pageIndex]` in your page-number UI when the array is present, and fall back to the integer page number when it’s not.

#### Why does my print output look blurry?

The most common cause is rendering at `scale: 1` (or relying on the default viewport scale). At 1×, PDF.js maps each PDF point to one CSS pixel — roughly 96 DPI when printed, well below the 300 DPI typically expected for print. Rerender to a print canvas at `scale: 3` or higher (≈288 DPI) before calling `window.print()`, and use CSS `@media print` to hide everything except the print container.

_See [Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) for built-in accessibility, presentation, and print support, or follow the [migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md) to switch from PDF.js. [Talk to Sales](https://www.nutrient.io/contact-sales/?=sdk) about your requirements._
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Extraction Confidence Scores](/blog/document-extraction-confidence-scores.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [How To Convert Html To Pdf Using Wkhtmltopdf And Csharp](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-csharp.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Merge Pdfs Using Javascript](/blog/how-to-merge-pdfs-using-javascript.md)
- [How To Ocr Pdfs In Linux](/blog/how-to-ocr-pdfs-in-linux.md)
- [How To Print Pdf In Csharp](/blog/how-to-print-pdf-in-csharp.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Javascript Pdf Editors](/blog/javascript-pdf-editors.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [or](/blog/merge-pdfs.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Building WCAG 2.2, Section 508, and PDF/UA-compliant PDFs with an SDK](/blog/pdf-accessibility.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Extraction Document Case Studies](/blog/pdf-extraction-document-case-studies.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Advanced Loading Streaming Workers](/blog/pdfjs-advanced-loading-streaming-workers.md)
- [Pdfjs Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [Using Yarn](/blog/react-pdf-editor.md)
- [or](/blog/sample-blog-updated.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Define the HTML part of the document.](/blog/top-10-ways-to-generate-pdfs-in-python.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [or](/blog/top-js-pdf-libraries.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Document Processing](/blog/what-is-document-processing.md)
- [What Is Intelligent Document Processing](/blog/what-is-intelligent-document-processing.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

