ScreenshotNeo

BlogHTML to image & PDF

How to Style an HTML PDF Viewer with CSS

Style the embedded PDF frame with CSS, or use PDF.js when you need control over the viewer toolbar, pages, and interaction.

By the ScreenshotNeo team1 October 20267 min read

Short answer: CSS can reliably style the box that contains an embedded PDF: its width, height, border, spacing, radius, and responsive layout. CSS from the parent page generally cannot redesign the browser’s built-in PDF toolbar or page controls inside an <iframe> or <object>. If you need to own the viewer interface, integrate a viewer such as PDF.js, whose viewer markup and styles are part of your application.

Choose the right viewer architecture

Approach What your CSS controls Use it when
Native iframe or object The embedding element and its surrounding layout. The browser PDF UI remains browser-dependent. You need a simple preview or document link.
PDF.js viewer/components Your integrated viewer markup, toolbar, pages, sidebar, and theme overrides. You need a branded or application-specific PDF interface.
Commercial SDK Vendor APIs for UI, toolbar, menus, and document workflows. You need features such as annotation, signing, or form filling and accept the vendor’s licensing model.

PDFObject’s quick start and API documentation demonstrate the container-and-iframe pattern. Mozilla’s PDF.js documentation describes the viewer as a starting point for building a custom viewer; its component example imports the viewer stylesheet and uses a .pdfViewer page container.

Style a native embedded PDF

Put the embed in a wrapper with an intentional height. The wrapper is the dependable styling surface.

<div class="pdf-frame">
  <iframe
    src="/documents/guide.pdf"
    title="Product guide"
    loading="lazy"
  >
    <p>
      Your browser cannot display this PDF inline.
      <a href="/documents/guide.pdf">Open or download the product guide</a>.
    </p>
  </iframe>
</div>
<p>
  <a href="/documents/guide.pdf" download>Download the product guide (PDF)</a>
</p>
:root {
  --pdf-border: #c9ced6;
  --pdf-radius: 0.5rem;
  --pdf-background: #f4f6f8;
}

.pdf-frame {
  width: 100%;
  height: min(75vh, 54rem);
  min-height: 24rem;
  background: var(--pdf-background);
  border: 1px solid var(--pdf-border);
  border-radius: var(--pdf-radius);
  overflow: hidden;
}

.pdf-frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

@media (max-width:  fortyrem) {
  .pdf-frame {
    height: 70vh;
    min-height: 20rem;
    border-radius: 0;
  }
}

Replace the invalid-looking media value above with a real CSS length such as 40rem; CSS does not accept words in a length. The corrected rule is:

@media (max-width: 40rem) {
  .pdf-frame {
    height: 70vh;
    min-height: 20rem;
    border-radius: 0;
  }
}

Use a specific title that identifies the document. Keep a normal open/download link outside the embed so users have a second route if inline rendering is unavailable.

Responsive sizing patterns

  • Viewport height: height: min(75vh, 54rem) prevents a very tall desktop viewer while remaining useful on smaller screens.
  • Fixed application panel: set the wrapper height from the layout, then let the iframe fill it with height: 100%.
  • Aspect-ratio preview: use aspect-ratio: 1 / 1.414 for an A-series page preview, but provide an overflow area for multi-page documents.
  • Full-screen mode: add a button that requests full screen on the wrapper if your product needs it; this changes the outer element, not the browser’s internal PDF controls.

What CSS cannot reliably change in a native viewer

An embedded PDF has an outer element and an inner document viewer. Parent-page selectors such as .pdf-frame iframe button do not reach controls rendered inside the browser’s PDF viewer. Browser implementations differ, so do not depend on a CSS selector to recolor or rearrange Chrome, Firefox, or Safari PDF toolbars.

You can style the surrounding heading, toolbar you build yourself, loading state, border, and fallback link. You cannot promise a cross-browser redesign of the native viewer UI from the embedding page.

Use object when you want fallback content

<object
  data="/documents/guide.pdf"
  type="application/pdf"
  class="pdf-object"
  aria-label="Product guide PDF"
>
  <p>
    Inline PDF viewing is unavailable.
    <a href="/documents/guide.pdf">Open the product guide</a>.
  </p>
</object>
.pdf-object {
  display: block;
  width: 100%;
  height: min(75vh, 54rem);
  border: 1px solid #c9ced6;
  border-radius: 0.5rem;
  overflow: hidden;
}

The fallback content inside object is useful when the embedded resource cannot be displayed. Keep the external link even if you primarily use an iframe.

Use PDF.js when the viewer UI must be yours

PDF.js has core, display, and viewer layers. Its distributed viewer includes HTML and CSS you can integrate and override. The component example shows the viewer stylesheet and a .pdfViewer page container.

<link rel="stylesheet" href="/pdfjs/web/pdf_viewer.css">
<div id="viewer-container" class="pdfViewer" aria-label="Product guide viewer"></div>
.app-pdf-shell {
  width: 100%;
  height: 80vh;
  min-height: 30rem;
  overflow: auto;
  background: #525659;
}

/* Scope overrides to your application shell. */
.app-pdf-shell .pdfViewer .page {
  margin: 1rem auto;
  box-shadow: 0 0.25rem 1rem rgb(0 0 0 / 25%);
}

.app-pdf-shell .toolbar {
  background: #172033;
  color: #fff;
}

@media (prefers-color-scheme: dark) {
  .app-pdf-shell {
    background: #20242b;
  }
}

Copy or import the PDF.js styles that match the exact release you deploy, then scope your overrides to your own shell. Internal selectors can change between releases; verify overrides after upgrades. If you only need a starting page or zoom, configure viewer state separately from CSS.

Initial page, zoom, destination, and sidebar

PDF.js documents URL-level options including page, zoom, nameddest, and pagemode. For example:

/pdfjs/web/viewer.html?file=%2Fdocuments%2Fguide.pdf#page=3&zoom=page-width&pagemode=none

These options select the initial view; they are not CSS properties. The viewer-options documentation is older, so check the options supported by the PDF.js release you ship.

Build an accessible PDF experience

  • Give an iframe a meaningful title, such as “Product guide”.
  • Provide an ordinary open or download link outside the viewer.
  • Do not communicate document meaning through color alone.
  • Keep keyboard focus visible for any controls you add around the viewer.
  • Announce loading and error states with appropriate status text.
  • Test the finished page with keyboard navigation and the assistive technology used by your audience.

Common problems and fixes

Symptom Likely cause Fix
The frame is tiny or zero height The iframe has height: 100% but its ancestors have no explicit height. Give the wrapper a height or min-height, then let the iframe fill it.
Toolbar styles do nothing The controls belong to the browser’s internal PDF viewer. Style the outer box, or integrate PDF.js for owned markup.
Rounded corners do not clip the PDF Overflow is visible on the wrapper. Set overflow: hidden on the wrapper.
PDF does not render inline Unsupported browser behavior, response headers, authentication, or a malformed PDF. Check the response, provide an open/download link, and consider a PDF.js integration.
PDF.js pages are unstyled The viewer stylesheet was not loaded or does not match the deployed viewer. Import the matching pdf_viewer.css and inspect the browser network panel.
Cross-origin file fails in PDF.js The PDF host does not permit the required cross-origin request. Serve the file from the application origin or configure the server’s CORS policy.
Deep link opens the wrong page Viewer options were treated as CSS or encoded incorrectly. Use the documented URL hash/query format for your PDF.js version and URL-encode the file parameter.
Mobile layout scrolls horizontally Fixed width, margins, or an unbreakable toolbar exceeds the viewport. Use width: 100%, flexible controls, and a responsive wrapper.

Performance, reliability, and cost considerations

  • Lazy-load a viewer below the fold, but do not hide the document’s fallback link.
  • Serve PDFs with stable URLs and appropriate caching headers when documents are immutable.
  • Large PDFs can consume memory while rendering; consider page ranges, pagination, or a server-generated preview.
  • Keep custom CSS scoped so a PDF.js upgrade does not unexpectedly affect the rest of the application.
  • Test with slow networks, blocked third-party resources, authenticated documents, and files containing many pages.

Or skip the browser setup

If your goal is a clean image or PDF of a web page rather than an interactive in-browser PDF viewer, ScreenshotNeo returns the capture from one request. Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing result. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for the full option list.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await fs.promises.writeFile('shot.webp', image);

ScreenshotNeo includes full-page capture, CSS-selector element capture, custom CSS and JavaScript, waits, headers, cookies, user agents, geolocation, PDF output, caching, signed links, async jobs, bulk capture, and a usage API. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Can CSS change Chrome’s PDF toolbar?

Not reliably from the parent page. The toolbar is inside the browser’s PDF viewer. Use PDF.js or another viewer whose markup you control.

Should I use iframe or object?

Both can embed a PDF. An iframe is straightforward; object provides useful fallback content inside the element. In either case, include a separate open/download link.

Can PDF.js use CSS variables for theming?

You can add variables in your application and apply them to scoped PDF.js overrides, but verify the selectors and structure against the exact PDF.js release you deploy.

Are page and zoom settings CSS?

No. PDF.js treats options such as page, zoom, named destination, and sidebar mode as viewer URL state.

How do I generate a PDF or screenshot without running a browser in my app?

Use an API such as ScreenshotNeo. Its one-call endpoint can return an image or PDF and handles consent banners, popups, chat widgets, waits, and other capture options for you.