ScreenshotAPI

Dark Mode Screenshots

Capture dark mode variants of websites using the colorScheme parameter.

Overview

Many websites support dark mode through the prefers-color-scheme CSS media feature. ScreenshotAPI can emulate this preference, allowing you to capture both light and dark variants of any page that supports it.

Basic Usage

Set the colorScheme parameter to "dark" or "light":

# Dark mode
curl "https://screenshotapi.to/api/v1/screenshot?url=https://github.com&colorScheme=dark" \
  -H "x-api-key: $SCREENSHOTAPI_KEY" \
  --output github-dark.png

# Light mode
curl "https://screenshotapi.to/api/v1/screenshot?url=https://github.com&colorScheme=light" \
  -H "x-api-key: $SCREENSHOTAPI_KEY" \
  --output github-light.png
async function captureWithScheme(url: string, scheme: 'light' | 'dark') {
  const params = new URLSearchParams({ url, colorScheme: scheme })

  const response = await fetch(
    `https://screenshotapi.to/api/v1/screenshot?${params}`,
    { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } }
  )

  return Buffer.from(await response.arrayBuffer())
}

// Capture both variants
const [lightBuffer, darkBuffer] = await Promise.all([
  captureWithScheme('https://github.com', 'light'),
  captureWithScheme('https://github.com', 'dark')
])

await fs.promises.writeFile('github-light.png', lightBuffer)
await fs.promises.writeFile('github-dark.png', darkBuffer)
import requests
import os

def capture_with_scheme(url: str, scheme: str) -> bytes:
    response = requests.get(
        "https://screenshotapi.to/api/v1/screenshot",
        params={"url": url, "colorScheme": scheme},
        headers={"x-api-key": os.environ["SCREENSHOTAPI_KEY"]}
    )
    response.raise_for_status()
    return response.content

# Capture both variants
for scheme in ["light", "dark"]:
    content = capture_with_scheme("https://github.com", scheme)
    with open(f"github-{scheme}.png", "wb") as f:
        f.write(content)

How It Works

When colorScheme=dark is set, ScreenshotAPI emulates the prefers-color-scheme: dark CSS media feature in the browser before taking the screenshot. This affects:

  • CSS media queries@media (prefers-color-scheme: dark) { ... }
  • CSS color-scheme — Elements using color-scheme: light dark
  • matchMedia JavaScript APIwindow.matchMedia('(prefers-color-scheme: dark)') returns true

This means any website that implements dark mode through standard CSS or JavaScript mechanisms will render in dark mode.

Capturing Both Variants

A common pattern is to capture both light and dark screenshots for comparison, documentation, or A/B testing:

interface DualScreenshotResult {
  light: Buffer
  dark: Buffer
  url: string
}

async function captureDualMode(url: string): Promise<DualScreenshotResult> {
  const [light, dark] = await Promise.all([
    captureWithScheme(url, 'light'),
    captureWithScheme(url, 'dark')
  ])

  return { light, dark, url }
}

async function captureWithScheme(url: string, scheme: 'light' | 'dark') {
  const params = new URLSearchParams({
    url,
    colorScheme: scheme,
    type: 'webp',
    quality: '85'
  })

  const response = await fetch(
    `https://screenshotapi.to/api/v1/screenshot?${params}`,
    { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } }
  )

  if (!response.ok) throw new Error(`Failed to capture ${scheme} mode`)
  return Buffer.from(await response.arrayBuffer())
}

// Capture and save
const result = await captureDualMode('https://tailwindcss.com')
await fs.promises.writeFile('tailwind-light.webp', result.light)
await fs.promises.writeFile('tailwind-dark.webp', result.dark)

Capturing both variants costs 2 credits (1 per screenshot). Use Promise.all to capture them in parallel for faster results.

Use Cases

App Store / Marketing Screenshots

Generate both light and dark variants for app store listings or marketing materials:

const pages = [
  { url: 'https://yourapp.com/dashboard', name: 'dashboard' },
  { url: 'https://yourapp.com/settings', name: 'settings' },
  { url: 'https://yourapp.com/analytics', name: 'analytics' }
]

for (const page of pages) {
  for (const scheme of ['light', 'dark'] as const) {
    const params = new URLSearchParams({
      url: page.url,
      colorScheme: scheme,
      width: '1280',
      height: '720',
      type: 'png'
    })

    const response = await fetch(
      `https://screenshotapi.to/api/v1/screenshot?${params}`,
      { headers: { 'x-api-key': process.env.SCREENSHOTAPI_KEY! } }
    )

    const buffer = Buffer.from(await response.arrayBuffer())
    await fs.promises.writeFile(`marketing/${page.name}-${scheme}.png`, buffer)
  }
}

OG Images with Dark Variant

Generate OG images that match the user's system preference:

// app/api/og/route.ts
export async function GET(request: NextRequest) {
  const title = request.nextUrl.searchParams.get('title') ?? ''
  const dark = request.nextUrl.searchParams.get('dark') === 'true'

  const templateUrl = `${APP_URL}/og-template?title=${encodeURIComponent(title)}`

  const params = new URLSearchParams({
    url: templateUrl,
    width: '1200',
    height: '630',
    colorScheme: dark ? 'dark' : 'light'
  })

  // ... fetch and return
}

Theme Testing in CI

Verify that dark mode renders correctly across your app:

const routes = ['/', '/about', '/pricing', '/blog']

for (const route of routes) {
  for (const scheme of ['light', 'dark'] as const) {
    const result = await api.capture({
      url: `${baseUrl}${route}`,
      colorScheme: scheme,
      width: 1440,
      height: 900
    })

    await writeFile(`screenshots/${route.replace('/', '_')}-${scheme}.png`, result.buffer)
  }
}

Troubleshooting

Dark mode not applied

Some sites implement dark mode through JavaScript class toggling (e.g., adding a dark class to the <html> element) rather than CSS media queries. The colorScheme parameter only affects the prefers-color-scheme media feature — it doesn't toggle JavaScript-based theme switches.

Workarounds:

  1. Check if the site also respects prefers-color-scheme alongside its JS toggle (many do).
  2. If the site only uses JS, you may need to screenshot a URL that forces dark mode (e.g., ?theme=dark if the site supports it).

Partial dark mode

Some pages may have components that don't respond to prefers-color-scheme (e.g., embedded iframes, third-party widgets). These will remain in their default color scheme.

Delayed theme application

If the site applies dark mode after a brief flash of light mode, add a short delay:

curl "https://screenshotapi.to/api/v1/screenshot?url=https://example.com&colorScheme=dark&delay=500" \
  -H "x-api-key: $SCREENSHOTAPI_KEY" \
  --output dark.png

On this page