ScreenshotNeo

BlogHow-to

How to Emulate Media Features in Puppeteer

Use Puppeteer to test dark mode, reduced motion, and screen or print styles. Learn the right API, runnable examples, and how to verify each setting.

By the ScreenshotNeo team4 October 20266 min read

Use page.emulateMediaFeatures() to set CSS media-feature preferences such as dark mode and reduced motion. Use page.emulateMediaType() to switch between screen and print styles. These APIs control different things: viewport and user-agent emulation use page.emulate(device), while vision-deficiency simulation uses page.emulateVisionDeficiency().

The examples below use Puppeteer’s documented APIs. Feature support and accepted values can depend on the Puppeteer and Chrome versions in your project, so verify less common features against the versions you run.

1. Set CSS media features

Pass an array of objects with name and value properties to page.emulateMediaFeatures(). For example, this sets dark color scheme and reduced motion:

await page.emulateMediaFeatures([
  { name: 'prefers-color-scheme', value: 'dark' },
  { name: 'prefers-reduced-motion', value: 'reduce' },
]);

Pages can read these preferences through CSS media queries, such as @media (prefers-color-scheme: dark), or through JavaScript’s matchMedia(). You can verify that the browser reports the intended values:

const state = await page.evaluate(() => ({
  dark: matchMedia('(prefers-color-scheme: dark)').matches,
  reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
}));

console.log(state); // { dark: true, reducedMotion: true }

Set the emulation before you inspect or capture the page. If application code reads a preference during startup and stores the result, set it before navigation as shown in the complete example below.

2. Choose the right Puppeteer API

What you need to emulate API What it changes
A CSS preference, such as dark mode or reduced motion page.emulateMediaFeatures(features) Named CSS media features
Screen or print styling page.emulateMediaType('screen' | 'print' | null) The CSS media type; null disables media-type emulation
Device viewport and user agent page.emulate(device) Device metrics and user agent
Vision-deficiency rendering page.emulateVisionDeficiency(type) A simulated vision deficiency

These controls address different browser state. Changing the viewport does not set prefers-color-scheme, and simulating a vision deficiency does not set a CSS preference such as prefers-reduced-motion.

3. Complete runnable example

This Node.js script opens a page, emulates dark mode and reduced motion, checks the resulting media queries, and saves a screenshot. It uses the Puppeteer package and a public example URL; replace the URL with your page when applying it to your project.

// Save as emulate-media.cjs
// Install Puppeteer with: npm install puppeteer
const puppeteer = require('puppeteer');

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

    await page.emulateMediaFeatures([
      { name: 'prefers-color-scheme', value: 'dark' },
      { name: 'prefers-reduced-motion', value: 'reduce' },
    ]);

    await page.setViewport({ width: 1440, height: 1000 });
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });

    const mediaState = await page.evaluate(() => ({
      dark: matchMedia('(prefers-color-scheme: dark)').matches,
      reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
    }));
    console.log('Emulated media state:', mediaState);

    await page.screenshot({ path: 'dark-reduced-motion.png', fullPage: true });
  } finally {
    await browser.close();
  }
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

The await page.emulateMediaFeatures(...) call happens before goto() so the page sees the preferences from its initial load. The verification step checks the browser’s media-query state; it does not prove that the site’s CSS or application logic implements those preferences correctly. Inspect the rendered result or assert the relevant page behavior in your own test.

4. Switch between screen and print

Use page.emulateMediaType() when the page has different CSS for the screen and print media types:

await page.emulateMediaType('print');
const printMatches = await page.evaluate(() => matchMedia('print').matches);
console.log(printMatches); // true

await page.emulateMediaType('screen');
const screenMatches = await page.evaluate(() => matchMedia('screen').matches);
console.log(screenMatches); // true

await page.emulateMediaType(null); // Disable CSS media-type emulation

To create a PDF with screen styling, select screen before calling page.pdf(). By default, page.pdf() generates a PDF using the print CSS media type. Printing can also modify colors; when exact print colors matter, review the CSS -webkit-print-color-adjust behavior documented by Puppeteer.

await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-styles.pdf', printBackground: true });

5. Reset or vary the emulated state

Set the feature values needed by each test. To check a light-mode state, for example, emulate prefers-color-scheme: light and verify it with matchMedia():

await page.emulateMediaFeatures([
  { name: 'prefers-color-scheme', value: 'light' },
]);

const isLight = await page.evaluate(() =>
  matchMedia('(prefers-color-scheme: light)').matches
);

If you need to return to the browser’s default state, consult the API documentation for the Puppeteer version in use and verify the resulting media-query values. For media type, passing null disables CSS media emulation. Do not assume that resetting media type also resets media features.

For a device-sized viewport and user agent, use a device descriptor with page.emulate(device). Puppeteer recommends applying device emulation before navigation because a site may not expect its size to change after loading. Use page.emulateVisionDeficiency(type) for a vision simulation such as deuteranopia, achromatopsia, blurredVision, or reducedContrast; use none to reset that simulation.

6. Troubleshooting

Symptom Likely cause Fix
matchMedia('(prefers-color-scheme: dark)').matches is false The feature was not set, the name or value is wrong, or the page is being checked in a different browser context. Call emulateMediaFeatures() on the same page before checking. Log the result of matchMedia() from that page and confirm the Puppeteer/Chrome version.
The query matches, but the page still looks light The site may not define dark-mode styles for that preference, or application code may override them. Inspect the page’s CSS and application state. A matching media query confirms browser preference state, not site implementation.
Reduced-motion query matches, but animations continue The site may not honor prefers-reduced-motion, or JavaScript may be driving motion separately. Check the site’s reduced-motion CSS and application logic. Test behavior as well as the query value.
The PDF uses print layout when screen layout was expected page.pdf() uses the print media type by default. Call await page.emulateMediaType('screen') before page.pdf().
Device layout differs after the page loads Device metrics or user agent were changed after navigation. Apply page.emulate(device) before navigation, then load the page.
A feature name or value is rejected or has no effect Not every feature/value combination is established by the examples in the API docs, and behavior can depend on the browser version. Check the Puppeteer API reference and the CSS/browser support for the exact feature and value in your project’s versions.

7. Performance, reliability, and cost

Media emulation sets browser state; the capture or test still has to load and render the page. Use a deliberate navigation wait condition for your page, and avoid waiting for network idle when the site keeps long-running requests open. Run related preference checks in the same browser session where practical, while ensuring each test sets the state it expects.

For reliable results, record the Puppeteer and Chrome versions used by CI, set the media state explicitly for each case, and verify it with matchMedia(). A media-query check confirms the emulated state, while a visual or behavioral assertion checks whether the page responds correctly. The dossier’s official documentation examples do not establish a complete compatibility matrix for all features and versions.

Puppeteer itself is software; no separate hardware or paid product is required by these APIs. Account for the runtime and infrastructure used to launch Chrome in your own environment. If maintaining browser setup is unnecessary for your use case, ScreenshotNeo offers a one-request screenshot API and an MCP server for AI agents.

Or skip the browser setup

For a hosted screenshot without managing Puppeteer and Chrome, try ScreenshotNeo. Its API accepts a URL and returns an image or PDF. See the ScreenshotNeo API documentation for request options.

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,
)
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}`);
  • Cookie banners are accepted and removed before the shot, along with known newsletter popups and chat widgets; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. The response includes X-Page-Verdict and X-Billed headers.
  • An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
  • The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free screenshots a month, with no card required.

Frequently asked questions

Does emulating dark mode change the operating system theme?

No. It changes the browser’s emulated CSS media-feature state for the page; it does not change the host operating system’s theme.

Can I use media emulation for screenshots and PDFs?

Yes. Set the required feature or media type before capturing. For PDFs, explicitly choose screen media if you want screen styles instead of the default print styles.

Does setting the viewport emulate media features?

No. A viewport controls page dimensions. Use emulateMediaFeatures() for CSS preferences such as dark mode and emulateMediaType() for screen or print.