ScreenshotNeo

BlogHow-to

Playwright Screenshot Shows the Wrong Color Scheme: Dark Mode Fix

Make Playwright screenshots render dark themes by emulating prefers-color-scheme before capture, and troubleshoot apps that still appear light.

By the ScreenshotNeo team4 October 20265 min read

If a Playwright screenshot looks light when you expect dark mode, emulate the browser’s prefers-color-scheme: dark preference before taking the screenshot. For a one-off page, call page.emulateMedia({ colorScheme: 'dark' }); for repeatable tests, set colorScheme: 'dark' in the test or project configuration. The page must respond to that preference for its appearance to change.

Fix one screenshot with page.emulateMedia

Set the media preference after creating the page and before capturing it. This complete example uses Playwright’s library API with Chromium:

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage();

await page.emulateMedia({ colorScheme: 'dark' });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'screenshot-dark.png', fullPage: true });

await browser.close();

The key line is page.emulateMedia({ colorScheme: 'dark' }). It changes the page’s emulated color-scheme preference. The screenshot call does not select or force a theme by itself.

Set dark mode for Playwright tests

For a test-specific default, set the option with test.use. The setting applies to tests in that scope:

import { test, expect } from '@playwright/test';

test.use({ colorScheme: 'dark' });

test('renders the dark color scheme', async ({ page }) => {
  await page.goto('https://example.com');
  await page.screenshot({ path: 'test-results/dark.png', fullPage: true });

  const isDark = await page.evaluate(
    () => matchMedia('(prefers-color-scheme: dark)').matches
  );
  expect(isDark).toBe(true);
});

To make dark mode the default for a whole project, configure the Playwright Test runner in playwright.config.ts:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    colorScheme: 'dark',
  },
});

Use the narrowest scope that matches the job: page-level emulation for a single capture, test configuration for selected tests, or project configuration when the entire project should use dark preference.

Verify the preference the page receives

If the screenshot still looks light, check the media query from inside the page. Run this after emulation and before capture:

const isDark = await page.evaluate(
  () => matchMedia('(prefers-color-scheme: dark)').matches
);
console.log({ isDark });

If isDark is true, Playwright has set the preference. That does not guarantee the site uses it: its CSS or application code must respond to prefers-color-scheme. For example, a site may define its dark colors in a media query:

@media (prefers-color-scheme: dark) {
  body {
    color: #f4f4f5;
    background: #18181b;
  }
}

This snippet is illustrative; the site being captured determines how its theme is implemented. Some applications instead use a saved preference or an in-app theme control. In that case, set the application’s own state as well as the browser media preference.

Choose the right configuration method

Method Scope Use it when
page.emulateMedia({ colorScheme: 'dark' }) One existing page A script needs a one-off dark screenshot.
test.use({ colorScheme: 'dark' }) Tests in a configuration scope A group of tests should share the preference.
use: { colorScheme: 'dark' } Playwright Test project The project should default to dark preference.
npx playwright codegen --color-scheme=dark <URL> Code generation session You are recording interactions against a dark-preference page.

The configuration and API options are documented in the Page API, Playwright emulation guide, test use options, and codegen documentation.

Common problems and fixes

Symptom Likely cause Fix
Screenshot is light and the media query check is false. The preference was not set on the page or test context used for capture, or it was set after the screenshot. Emulate dark mode on the same page before navigating or capturing; in tests, set colorScheme in the applicable test.use or project use configuration.
The check is true, but the page is light. The app may not style itself from prefers-color-scheme. Inspect the app’s theme behavior. If it uses an in-app toggle or persisted setting, set that application state too.
Some areas are dark and others stay light. Different components may use different theme rules, or the application may apply its own theme state selectively. Check the site’s CSS and theme initialization for components that do not follow the media preference; set the app-level theme if required.
The page changes theme after the screenshot. The app may apply theme state asynchronously during startup. Wait for the relevant theme element or application-ready state before capturing. A selector wait is more targeted than an arbitrary delay when the app exposes a stable element.

Capture timing, reliability, and cost

Apply the preference before the page renders its final state so the application can react to it. If the app selects its theme during startup, set the preference before navigation, then wait for the app’s own ready signal or a relevant selector before capturing. A screenshot taken too early can show a transient theme even when the preference is correct.

For repeatable output, keep the browser, viewport, application state, and capture timing consistent along with the color scheme. The media preference controls what the page receives; it does not make the page’s own theme logic deterministic. If screenshots run in CI, record the preference and verify it when a visual result is unexpectedly light.

Playwright itself has no per-screenshot API charge described in the cited documentation. Your practical costs depend on where browser automation runs and how long it takes. For repeated captures, avoid unnecessary waits and use a condition tied to the page’s actual readiness where possible.

Or skip the browser setup

ScreenshotNeo is a website screenshot API: send one GET request with a URL to receive a PNG, JPEG, WebP, or PDF. The API supports dark mode, and its clean-shot flow accepts cookie or consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the page verdict and billing status in headers. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.

Here is the one-call cURL example; replace the URL with the page you need. See the ScreenshotNeo API documentation for the request options.

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

There are 1,000 screenshots a month on the free plan with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Sign up for 1,000 free screenshots a month, with no card.

FAQ

Does colorScheme make any website dark?

No. It sets the browser’s prefers-color-scheme preference. The application must use that preference or have its own theme state configured.

Should I use a screenshot option to choose dark mode?

No. Set the media preference with Playwright before capture; page.screenshot() takes the image of the page’s current rendered state.

Can I record a dark-mode flow with codegen?

Yes. Playwright documents npx playwright codegen --color-scheme=dark <URL> for code generation with dark color-scheme preference.