Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Puppeteer’s page.addStyleTag() method to inject CSS into a page’s main frame. Pass your rules as content for an inline <style> element, or pass a stylesheet address as url for a linked <link rel="stylesheet">. If the target is an iframe, select that frame and call frame.addStyleTag() instead.

Inject inline CSS with page.addStyleTag()

The smallest working example launches a browser, opens a page, adds a style element, and then continues with the modified document:

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({headless: true});
  const page = await browser.newPage();

  await page.goto('https://example.com', {waitUntil: 'networkidle2'});

  await page.addStyleTag({
    content: `
      body {
        background: #f0f0f0;
        color: #222;
        font-family: system-ui, sans-serif;
      }
      h1 {
        color: #123456;
      }
    `,
  });

  await page.screenshot({path: 'styled-page.png', fullPage: true});
  await browser.close();
})();

addStyleTag resolves to a handle for the element Puppeteer inserted. You can retain that handle if you need to inspect or remove the style element later. The Page method is a shortcut for adding the style to the page’s main frame.

Use a reusable CSS string

const css = `
  body { background: #f0f0f0; }
  h1 { color: #123456; }
  .debug-outline { outline: 2px solid #e11; }
`;

const styleHandle = await page.addStyleTag({content: css});

Keep the template literal in your application when the rules are generated dynamically. CSS still follows normal cascade rules: a selector with greater specificity, an inline declaration, or a later rule can override your injected declaration. Add specificity deliberately rather than reaching for !important everywhere.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Load an external stylesheet

For CSS that already lives at a URL, pass url:

await page.addStyleTag({
  url: 'https://example.com/styles.css',
});

Puppeteer creates a stylesheet link in the document. The remote server must be reachable from the browser, and the URL must return CSS suitable for a stylesheet. A blocked request, redirect problem, authentication requirement, or non-CSS response can prevent the rules from taking effect. Inline content avoids that additional network dependency.

Style an iframe with frame.addStyleTag()

page.addStyleTag() affects the main browsing context only. An iframe has its own document and must be addressed through its selected Frame object. Puppeteer documents the corresponding Frame API for injecting either a style element or a stylesheet link.

await page.goto('https://example.com/dashboard', {waitUntil: 'networkidle2'});

const frame = page.frames().find(f => f.url().includes('/embedded-panel'));
if (!frame) {
  throw new Error('Embedded panel frame was not found');
}

await frame.addStyleTag({
  content: `
    body { background: #111; color: #eee; }
    .notice { border-color: #6cf; }
  `,
});

Locate the frame after navigation or after the iframe appears. A cross-origin iframe is still a separate browsing context; use Puppeteer’s frame object rather than trying to select its elements from the parent page.

When to use addStyleTag versus evaluate

Need Preferred API Reason
Inject inline CSS into the main page page.addStyleTag({content}) Expresses the stylesheet operation directly and inserts a <style> element.
Link a stylesheet by URL page.addStyleTag({url}) Inserts a <link rel="stylesheet"> element.
Inject CSS into a selected iframe frame.addStyleTag(options) Targets that frame’s document rather than the parent page.
Perform arbitrary DOM work page.evaluate() Runs a function in the page context and returns its result.

For example, use evaluate when you must create several nodes, calculate styles from page data, or alter the DOM as well as its presentation:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
await page.evaluate(() => {
  const banner = document.createElement('div');
  banner.textContent = 'Preview mode';
  banner.style.cssText = 'position:fixed;top:0;right:0;z-index:99999;padding:8px;background:#ff0;color:#000';
  document.body.appendChild(banner);
});

For straightforward CSS injection, addStyleTag is clearer and avoids embedding DOM-construction details in an evaluation function.

Apply styles at the right time

Navigate before injecting

Inject after page.goto has reached the state your capture or test needs. A later navigation replaces the document and removes the injected element, so add the style again after navigating.

await page.goto('https://example.com', {waitUntil: 'domcontentloaded'});
await page.addStyleTag({content: 'body { opacity: .8; }'});

Wait for dynamic content

If a single-page application renders the target component after navigation, wait for a selector before injecting or capturing:

await page.goto('https://example.com/app', {waitUntil: 'networkidle2'});
await page.waitForSelector('.report');
await page.addStyleTag({content: '.report { background: white; }'});

Rules can be present before an element is rendered; waiting is useful when your next operation must also interact with that element or take a screenshot of its final state.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Verify that the rule won the cascade

const background = await page.$eval('body', el => getComputedStyle(el).backgroundColor);
console.log(background);

When the computed value is unchanged, inspect selector specificity, inline styles, styles loaded later, shadow DOM boundaries, and whether the selector matches the intended frame.

Capture a styled result

Style injection changes the live document. You can then use any Puppeteer output method, such as a screenshot or PDF:

await page.addStyleTag({
  content: `
    @media print {
      nav, .cookie-banner { display: none !important; }
      body { color: #000; }
    }
  `,
});

await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  printBackground: true,
});

For screenshots, use page.screenshot after the style promise resolves. If the page changes after injection, wait for the relevant selector or application state before capturing.

Common failures and fixes

“The method is not a function”

  • Check that you are calling it on a Puppeteer Page or Frame, not on a browser or element handle.
  • Confirm that your installed Puppeteer release exposes the API; the official reference pages display version labels from 25.10.0 to 25.12.0, so check the documentation matching your installed release.

The CSS has no visible effect

  • Confirm the selector matches with page.$ or page.$eval.
  • Inspect computed styles to identify an overriding rule.
  • Ensure the element is in the main page, not an iframe or shadow root.
  • Remember that a later navigation discards the injected style.

An external URL does not load

  • Use an absolute HTTPS URL and verify that it is reachable from the browser process.
  • Check redirects, authentication, certificate errors, and the response’s content type.
  • Use inline content when deterministic, network-independent rendering matters.

The iframe cannot be found

  • Wait for the iframe element or its URL to appear.
  • Match the frame by a stable URL or name rather than relying on array position.
  • After a frame reloads, reacquire the current Frame object before injecting.

Styles disappear in a single-page application

Some applications replace the document or recreate an iframe during routing. Attach your injection to the navigation or component-ready step and run it again whenever that browsing context is replaced.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Performance, reliability, and security considerations

  • Inline CSS adds no stylesheet request and is usually the most predictable option for one-off rendering.
  • External CSS can be shared across captures, but its availability and response time become part of the rendering path.
  • Keep injected rules scoped to a wrapper or component when possible; broad selectors such as * can alter controls, dialogs, and third-party widgets unexpectedly.
  • Do not interpolate untrusted text directly into CSS or JavaScript. Validate generated values and treat page content as untrusted input.
  • When producing PDFs, include printBackground: true if the intended design depends on background colors or images.

Or skip the browser setup

If your goal is a clean screenshot rather than a Puppeteer test or a custom browser workflow, ScreenshotNeo provides a website screenshot API. It accepts a URL in one GET request and can return PNG, JPEG, WebP, or PDF. You can pass custom CSS and JavaScript, wait for a selector, delay, or network idle, select an element, choose a device or viewport, and set options such as dark mode, retina scale, headers, cookies, timezone, geolocation, hidden selectors, and resource blocking. Full-page capture can load lazy images.

Example with cURL (the complete option list is in the ScreenshotNeo documentation):

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

The equivalent Python request is:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

In Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. Create a free ScreenshotNeo account to try it without a card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Version and documentation note

Puppeteer’s API pages can display different release labels over time. The behavior described here is the documented Page.addStyleTag, Frame.addStyleTag, and Page.evaluate interface; consult the reference for the version installed in your project: Page.addStyleTag, Frame.addStyleTag, Page.evaluate, Page interactions, and the Page class.

Frequently Asked Questions

Does addStyleTag modify the site permanently?

No. It modifies only the current browser document. A reload or navigation creates a new document without the injected element.

Can I inject CSS into a shadow root with addStyleTag?

The Page and Frame methods target their document. A shadow root requires code that runs in the component’s context and follows that component’s encapsulation rules.

Should I use content or url for repeatable tests?

Use content when you need a self-contained, deterministic stylesheet. Use url when the stylesheet is maintained separately and its network availability is acceptable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.