build69
build69  /  Components  /  Media & Files  /  DocumentViewer

DocumentViewer

v1.0.13Updated 20 Oct 2026

Modal viewer for ONE stored document (fb_781415ea): fetches it WITH credentials, renders a PDF in an iframe, an image inline, and an explicit "cannot be shown here" state + Download for office types. Footer: print through the frame (so the DOCUMENT prints), download under the stored filename, open-in-new-tab, close. Also accepts app-composed markup (srcDoc) for client-built print sheets. DocumentViewerSurface is the dialog-less inner surface for embedding / SSR.

Preview

Live · 7 stories
DefaultOpen ↗
▸Why this is recommendeddocumentpdfpreviewfile+7

The default, out-of-the-box DocumentViewer.

Recommended for+14
  • ✓Opening a stored attachment / document from a record while the reader stays mid-task (the owner rule: popup dialogs, never new tabs)
  • ✓Previewing uploaded PDFs and images from a credentialed API
  • ✓Client-composed print sheets (srcDoc) with a real print path
  • ✓Showing a file whose name came from whoever uploaded it, where a disguised name must not be able to save something else to the reader disk
Tags+6
documentpdfpreviewfileviewerprintdownloadiframemodalattachmentdefault
Features+4
  • Fetch-then-render with credentials: include — an iframe pointed at the URL would render a login page or JSON envelope as if it were the document; fetching first turns a refusal (403 / business rule) into the app's own inline error WITH the server's message and a Retry
  • States: skeleton while fetching (never a spinner with text), inline error with the server's message + HTTP status + Retry, and an explicit unsupported-type state with Download — never an empty frame
  • Type resolution: mimeType prop > response Content-Type > filename extension; office types (doc/docx/xls/xlsx/ppt/pptx) resolve to the unsupported state by design
  • Print through frame.contentWindow so the printed artifact is the document, not the page hosting it
  • Download saves the blob under the stored fileName; open-in-new-tab opens the blob URL
  • fileName is treated as UNTRUSTED (it is whatever an uploader typed): control characters, bidi overrides and zero-width characters are stripped before it is shown as the headline, the frame title or the image alt text, so a name carrying U+202E can no longer read as scanexe.pdf while being scan<U+202E>fdp.exe. The download attribute gets a second, filesystem-level cleaning: path separators and the characters Windows refuses become underscores (a/b.pdf saves as a_b.pdf, ../../x cannot leave the downloads folder), a leading dot cannot write a hidden file, an over-long name is cut but keeps its extension, and a name that cleans away entirely falls back to the unnamedFile label instead of letting the browser invent one. The extension is never rewritten, so what the reader sees is what lands on disk
  • The blob URL carries the type the component RESOLVED, never the type the response asserted: an HTML body behind mimeType="application/pdf" would otherwise become a text/html blob in the deliberately unsandboxed PDF frame and run as script in the page origin; an unresolvable type is pinned to application/octet-stream. Side benefit — a real PDF served as octet-stream is now framed instead of offered as a save
  • srcDoc accepts app-composed markup (client-built print sheets) — takes precedence over src; print-only (no blob, so no download / open-in-tab)
  • Object-URL hygiene: blob URLs are revoked on src change, close and unmount; the in-flight fetch is aborted
  • i18n labels prop (DocumentViewerLabels — every built-in string overridable, including the accessible name of the loading skeleton and the unnamedFile fallback; unsupportedDescription and errorStatus are {token} templates)
Not recommended for−2
  • ✕Browsing many files (use FileBrowser)
  • ✕Video playback (use VideoPlayer)
  • ✕Editing rich text or markup (this is a viewer, not an editor)

Install

Ask your agent, or call the MCP tool directly. The resolved bundle is written into your project.

get_registry_item({ name: "DocumentViewer" })

Usage

import { DocumentViewer } from '@/components/media/document-viewer';

<DocumentViewer
  open={previewOpen}
  onOpenChange={setPreviewOpen}
  src={`/api/documents/${doc.id}/content`}
  fileName={doc.fileName}
  mimeType={doc.mimeType}
/>

Tick variants in the left menu — each adds a tab with that variant’s real story source, the same code the preview above renders.

Props

PropTypeRequired
open

Dialog visibility (controlled)

booleanyes
onOpenChange

Open-state change callback (✕, footer Close, Escape, outside click)

(open: boolean) => voidyes
src

Stored-document URL; fetched lazily (only while open) with credentials and rendered from a blob

string—
srcDoc

App-composed markup rendered verbatim in the frame (client-built print sheets). Takes precedence over src; print-only mode — no download / open-in-tab

string—
fileName

Stored filename: the headline, the frame title, the download name, and the extension fallback for type resolution. Treated as untrusted host data — invisible and direction-reordering characters are stripped before it is shown, and the saved name additionally has path separators and filesystem-reserved characters neutralised. The extension is never rewritten

stringyes
mimeType

MIME type hint; wins over the response Content-Type and the extension. An office mimeType shows the unsupported state immediately (no skeleton flash)

string—
title

Dialog headline; defaults to fileName

React.ReactNode—
fetchInit

Merged over { credentials: 'include' } — credentialed fetch is the default and the point

RequestInit—
labels

Override any built-in UI string (print/download/openInNewTab/close/errorTitle/errorStatus/retry/unsupportedTitle/unsupportedDescription/documentAria/loading/unnamedFile) for i18n. unnamedFile is the headline and the saved name for a document whose fileName carries nothing readable

Partial<DocumentViewerLabels>—
className

Applied to the DialogContent (the surface variant applies it to its root)

string—

Related