Add blog post: How to Capture Dark Mode Screenshots Automatically
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m37s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m37s
- New blog post covering darkMode parameter, CSS injection, hideSelectors, dual OG images - Code examples in cURL, Node.js, and Python - Blog index updated with new post - Sitemap updated with new URL - Tests already committed (474 passing)
This commit is contained in:
parent
990b6d4f95
commit
e7ef9d74c4
3 changed files with 282 additions and 0 deletions
|
|
@ -93,6 +93,13 @@ footer{border-top:1px solid var(--border);padding:48px 24px 32px;background:var(
|
|||
</div>
|
||||
|
||||
<div class="blog-grid">
|
||||
<article class="blog-card">
|
||||
<div class="meta"><span>March 6, 2026</span><span>6 min read</span></div>
|
||||
<h2><a href="/blog/dark-mode-screenshots">How to Capture Dark Mode Screenshots Automatically</a></h2>
|
||||
<p>Dark mode isn't a trend anymore — it's the default. Learn how to capture both light and dark screenshots with a single API parameter, inject custom dark themes, and generate dual OG images for social media.</p>
|
||||
<a href="/blog/dark-mode-screenshots" class="read-more">Read article →</a>
|
||||
</article>
|
||||
|
||||
<article class="blog-card">
|
||||
<div class="meta"><span>March 3, 2026</span><span>7 min read</span></div>
|
||||
<h2><a href="/blog/automating-og-images">Automating OG Image Generation with Screenshot APIs</a></h2>
|
||||
|
|
|
|||
274
public/blog/dark-mode-screenshots.html
Normal file
274
public/blog/dark-mode-screenshots.html
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>How to Capture Dark Mode Screenshots Automatically — SnapAPI Blog</title>
|
||||
<meta name="description" content="Learn how to capture dark mode screenshots with a single API parameter. Generate both light and dark screenshots for testing, monitoring, and social media previews.">
|
||||
<link rel="canonical" href="https://snapapi.eu/blog/dark-mode-screenshots">
|
||||
<meta property="og:title" content="How to Capture Dark Mode Screenshots Automatically">
|
||||
<meta property="og:description" content="Capture dark mode screenshots with a single API parameter. Perfect for testing, monitoring, and generating social media previews in both themes.">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="https://snapapi.eu/blog/dark-mode-screenshots">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="How to Capture Dark Mode Screenshots Automatically">
|
||||
<meta name="twitter:description" content="Capture dark mode screenshots with one API parameter. Test both themes, generate dual OG images.">
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📸</text></svg>">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{--bg:#0a0e17;--bg2:#0f1420;--card:#141a28;--card-hover:#1a2235;--border:#1e2a3f;--border-light:#2a3752;--text:#f0f2f7;--text-secondary:#94a3c0;--muted:#6b7a96;--primary:#4f8fff;--primary-light:#6da3ff;--primary-dark:#3a6fd8;--accent:#10b981;--purple:#a78bfa;--orange:#f59e0b;--gradient:linear-gradient(135deg,#4f8fff 0%,#a78bfa 50%,#ec4899 100%);--radius:12px;--radius-lg:16px}
|
||||
html{scroll-behavior:smooth}
|
||||
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.6;-webkit-font-smoothing:antialiased}
|
||||
a{color:var(--primary-light);text-decoration:none;transition:color .2s}
|
||||
a:hover{color:var(--primary)}
|
||||
nav{position:sticky;top:0;z-index:100;background:rgba(10,14,23,0.85);backdrop-filter:blur(20px);border-bottom:1px solid rgba(30,42,63,0.5);padding:0 24px}
|
||||
.nav-inner{max-width:1180px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;height:64px}
|
||||
.nav-logo{font-size:1.15rem;font-weight:800;display:flex;align-items:center;gap:8px;color:var(--text)}
|
||||
.nav-logo span{background:var(--gradient);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
|
||||
.nav-links{display:flex;gap:32px;align-items:center}
|
||||
.nav-links a{color:var(--muted);font-size:.9rem;font-weight:500}
|
||||
.nav-links a:hover{color:var(--text)}
|
||||
.btn{display:inline-flex;align-items:center;gap:8px;padding:12px 28px;border-radius:10px;font-weight:600;font-size:.95rem;border:none;cursor:pointer;transition:all .2s;font-family:inherit}
|
||||
.btn-primary{background:var(--primary);color:#fff;box-shadow:0 4px 20px rgba(79,143,255,0.3)}
|
||||
.btn-primary:hover{background:var(--primary-dark);transform:translateY(-1px);color:#fff}
|
||||
.btn-sm{padding:8px 18px;font-size:.85rem}
|
||||
.nav-mobile{display:none;background:none;border:none;color:var(--text);font-size:1.5rem;cursor:pointer}
|
||||
@media(max-width:768px){.nav-links{display:none;flex-direction:column;position:absolute;top:64px;left:0;right:0;background:rgba(10,14,23,0.97);backdrop-filter:blur(20px);border-bottom:1px solid var(--border);padding:16px 24px;gap:16px;z-index:99}.nav-links.show{display:flex}.nav-mobile{display:block}}
|
||||
|
||||
.article{max-width:740px;margin:0 auto;padding:80px 24px 100px}
|
||||
.article .breadcrumb{font-size:.85rem;color:var(--muted);margin-bottom:32px}
|
||||
.article .breadcrumb a{color:var(--muted)}
|
||||
.article .breadcrumb a:hover{color:var(--text)}
|
||||
.article h1{font-size:2.5rem;font-weight:900;line-height:1.2;margin-bottom:16px}
|
||||
.article .meta{font-size:.85rem;color:var(--muted);margin-bottom:48px;display:flex;gap:16px}
|
||||
.article h2{font-size:1.5rem;font-weight:700;margin:48px 0 16px;color:var(--text)}
|
||||
.article h3{font-size:1.2rem;font-weight:600;margin:32px 0 12px;color:var(--text)}
|
||||
.article p{color:var(--text-secondary);line-height:1.8;margin-bottom:20px;font-size:1.05rem}
|
||||
.article ul,.article ol{color:var(--text-secondary);margin:0 0 20px 24px;line-height:1.8}
|
||||
.article li{margin-bottom:8px}
|
||||
.article code{font-family:'JetBrains Mono',monospace;background:var(--card);padding:2px 8px;border-radius:4px;font-size:.9rem;color:var(--primary-light)}
|
||||
.article pre{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin:0 0 24px;overflow-x:auto}
|
||||
.article pre code{background:none;padding:0;font-size:.85rem;line-height:1.7;color:var(--text-secondary)}
|
||||
.article blockquote{border-left:3px solid var(--primary);padding:16px 24px;margin:0 0 24px;background:var(--card);border-radius:0 var(--radius) var(--radius) 0}
|
||||
.article blockquote p{margin:0;color:var(--text-secondary);font-style:italic}
|
||||
.article .cta-box{background:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:32px;margin:48px 0;text-align:center}
|
||||
.article .cta-box h3{margin-top:0;font-size:1.3rem}
|
||||
.article .cta-box p{margin-bottom:20px}
|
||||
|
||||
footer{border-top:1px solid var(--border);padding:48px 24px 32px;background:var(--bg2)}
|
||||
.footer-grid{max-width:1180px;margin:0 auto;display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:40px;margin-bottom:40px}
|
||||
.footer-brand h4{font-size:1.1rem;font-weight:800;margin-bottom:12px;display:flex;align-items:center;gap:8px}
|
||||
.footer-brand p{color:var(--muted);font-size:.85rem;line-height:1.6;max-width:280px}
|
||||
.footer-col h5{font-size:.8rem;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);margin-bottom:16px}
|
||||
.footer-col a{display:block;color:var(--text-secondary);font-size:.88rem;padding:4px 0}
|
||||
.footer-col a:hover{color:var(--text)}
|
||||
.footer-bottom{max-width:1180px;margin:0 auto;padding-top:24px;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;font-size:.8rem;color:var(--muted)}
|
||||
@media(max-width:768px){.footer-grid{grid-template-columns:1fr}}
|
||||
|
||||
.skip-link{position:absolute;top:-100%;left:50%;transform:translateX(-50%);background:var(--primary);color:#fff;padding:12px 24px;border-radius:0 0 8px 8px;font-weight:600;font-size:.9rem;z-index:1000;transition:top .2s}
|
||||
.skip-link:focus{top:0}
|
||||
</style>
|
||||
<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"How to Capture Dark Mode Screenshots Automatically","description":"Learn how to capture dark mode screenshots with a single API parameter. Generate both light and dark screenshots for testing and social media.","datePublished":"2026-03-06","author":{"@type":"Organization","name":"SnapAPI"},"publisher":{"@type":"Organization","name":"SnapAPI","url":"https://snapapi.eu"},"url":"https://snapapi.eu/blog/dark-mode-screenshots","mainEntityOfPage":"https://snapapi.eu/blog/dark-mode-screenshots"}</script>
|
||||
</head>
|
||||
<body>
|
||||
<a href="#main-content" class="skip-link">Skip to content</a>
|
||||
<header>
|
||||
<nav>
|
||||
<div class="nav-inner">
|
||||
<a href="/" class="nav-logo">📸 <span>SnapAPI</span></a>
|
||||
<div class="nav-links">
|
||||
<a href="/#features">Features</a>
|
||||
<a href="/pricing">Pricing</a>
|
||||
<a href="/docs">API Docs</a>
|
||||
<a href="/blog">Blog</a>
|
||||
<a href="/#pricing" class="btn btn-primary btn-sm">Get API Key</a>
|
||||
</div>
|
||||
<button class="nav-mobile" onclick="document.querySelector('.nav-links').classList.toggle('show')" aria-label="Menu">☰</button>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main id="main-content">
|
||||
<article class="article">
|
||||
<div class="breadcrumb"><a href="/">Home</a> → <a href="/blog">Blog</a> → Dark Mode Screenshots</div>
|
||||
|
||||
<h1>How to Capture Dark Mode Screenshots Automatically</h1>
|
||||
<div class="meta"><span>March 6, 2026</span><span>6 min read</span></div>
|
||||
|
||||
<p>Dark mode isn't a trend anymore — it's the default for most developers and a growing majority of users. Studies show that over 80% of developers work in dark mode, and roughly 70% of smartphone users have enabled it system-wide. If your app or website supports dark mode, you need to test, monitor, and preview both themes.</p>
|
||||
|
||||
<p>But capturing dark mode screenshots programmatically is surprisingly annoying. Until now.</p>
|
||||
|
||||
<h2>The Problem: Dark Mode Is Harder Than It Looks</h2>
|
||||
|
||||
<p>Websites implement dark mode in different ways. Some use the <code>prefers-color-scheme</code> CSS media query. Others toggle a class on the body element via JavaScript. Some use CSS custom properties that switch based on a data attribute. And some use all three simultaneously.</p>
|
||||
|
||||
<p>If you're running a headless browser to take screenshots, you need to handle all of these. With Puppeteer or Playwright, that means:</p>
|
||||
|
||||
<ul>
|
||||
<li>Emulating the <code>prefers-color-scheme: dark</code> media feature</li>
|
||||
<li>Waiting for JavaScript theme toggles to apply</li>
|
||||
<li>Handling race conditions where CSS transitions haven't finished</li>
|
||||
<li>Managing different browser contexts for light vs. dark screenshots</li>
|
||||
</ul>
|
||||
|
||||
<p>That's a lot of boilerplate for what should be a simple toggle.</p>
|
||||
|
||||
<h2>The Solution: One Parameter</h2>
|
||||
|
||||
<p>SnapAPI's <code>darkMode</code> parameter handles all of this for you. Set it to <code>true</code>, and the browser emulates <code>prefers-color-scheme: dark</code> before loading the page. Any site that respects the system preference will render in dark mode automatically.</p>
|
||||
|
||||
<h3>cURL</h3>
|
||||
<pre><code>curl -X POST https://snapapi.eu/v1/screenshot \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"url": "https://github.com", "darkMode": true}' \
|
||||
--output github-dark.png</code></pre>
|
||||
|
||||
<h3>Node.js</h3>
|
||||
<pre><code>import SnapAPI from 'snapapi';
|
||||
|
||||
const client = new SnapAPI('YOUR_API_KEY');
|
||||
|
||||
// Light mode (default)
|
||||
const light = await client.screenshot({ url: 'https://github.com' });
|
||||
|
||||
// Dark mode — just add darkMode: true
|
||||
const dark = await client.screenshot({
|
||||
url: 'https://github.com',
|
||||
darkMode: true,
|
||||
});
|
||||
|
||||
fs.writeFileSync('github-light.png', light);
|
||||
fs.writeFileSync('github-dark.png', dark);</code></pre>
|
||||
|
||||
<h3>Python</h3>
|
||||
<pre><code>from snapapi import SnapAPI
|
||||
|
||||
client = SnapAPI("YOUR_API_KEY")
|
||||
|
||||
# Capture both themes
|
||||
light = client.screenshot(url="https://github.com")
|
||||
dark = client.screenshot(url="https://github.com", dark_mode=True)
|
||||
|
||||
with open("github-light.png", "wb") as f:
|
||||
f.write(light)
|
||||
with open("github-dark.png", "wb") as f:
|
||||
f.write(dark)</code></pre>
|
||||
|
||||
<h2>Going Further: Custom Dark Themes</h2>
|
||||
|
||||
<p>Not every site has built-in dark mode support. For those cases, you can combine <code>darkMode</code> with SnapAPI's <code>css</code> parameter to inject your own dark theme:</p>
|
||||
|
||||
<pre><code>curl -X POST https://snapapi.eu/v1/screenshot \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"url": "https://example.com",
|
||||
"darkMode": true,
|
||||
"css": "body { background: #1a1a2e !important; color: #eee !important; } a { color: #6da3ff !important; }"
|
||||
}' \
|
||||
--output example-forced-dark.png</code></pre>
|
||||
|
||||
<p>This is useful for generating consistent dark-themed previews across sites that don't natively support it.</p>
|
||||
|
||||
<h2>Cleaning Up Screenshots</h2>
|
||||
|
||||
<p>Real-world screenshots often include cookie banners, notification popups, and chat widgets that clutter the image. Use <code>hideSelectors</code> to remove them before capture:</p>
|
||||
|
||||
<pre><code>{
|
||||
"url": "https://example.com",
|
||||
"darkMode": true,
|
||||
"hideSelectors": ["#cookie-banner", ".chat-widget", ".notification-popup"],
|
||||
"css": "body { overflow: hidden !important; }"
|
||||
}</code></pre>
|
||||
|
||||
<p>Combined with dark mode, this gives you clean, professional screenshots every time.</p>
|
||||
|
||||
<h2>Use Case: Dual OG Images for Social Media</h2>
|
||||
|
||||
<p>One compelling use case is generating both light and dark Open Graph images for your website. Some platforms and apps display previews differently based on the user's system theme. By generating both variants, you can serve the right one via <code>og:image</code> based on context.</p>
|
||||
|
||||
<pre><code>// Generate OG images for all your pages in both themes
|
||||
const pages = ['/about', '/pricing', '/blog'];
|
||||
|
||||
for (const page of pages) {
|
||||
const url = `https://yoursite.com${page}`;
|
||||
|
||||
// Light variant
|
||||
await client.screenshot({
|
||||
url,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
format: 'jpeg',
|
||||
quality: 85,
|
||||
});
|
||||
|
||||
// Dark variant
|
||||
await client.screenshot({
|
||||
url,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
format: 'jpeg',
|
||||
quality: 85,
|
||||
darkMode: true,
|
||||
});
|
||||
}</code></pre>
|
||||
|
||||
<p>Automate this with a cron job or CI pipeline, and your social previews are always up to date — in both themes.</p>
|
||||
|
||||
<h2>When to Use Each Approach</h2>
|
||||
|
||||
<ul>
|
||||
<li><strong><code>darkMode: true</code></strong> — for sites with native <code>prefers-color-scheme</code> support (GitHub, MDN, most modern sites)</li>
|
||||
<li><strong><code>darkMode</code> + <code>css</code></strong> — for forcing a dark look on sites without dark mode support</li>
|
||||
<li><strong><code>hideSelectors</code></strong> — for removing UI clutter regardless of theme</li>
|
||||
<li><strong><code>js</code> parameter</strong> — for triggering JavaScript-based theme toggles before capture</li>
|
||||
</ul>
|
||||
|
||||
<p>All of these parameters work together. You can combine <code>darkMode</code>, <code>css</code>, <code>hideSelectors</code>, and <code>js</code> in a single request — no multiple API calls needed.</p>
|
||||
|
||||
<p>Check our <a href="/docs">API documentation</a> for the complete parameter reference, or see how we <a href="/compare">compare to other screenshot APIs</a> on feature coverage.</p>
|
||||
|
||||
<div class="cta-box">
|
||||
<h3>Try Dark Mode Screenshots</h3>
|
||||
<p>Test it right now with our <a href="/#playground">playground</a> — no API key needed. When you're ready for clean, unwatermarked screenshots, <a href="/pricing">pick a plan</a> that fits your usage.</p>
|
||||
<a href="/pricing" class="btn btn-primary">View Plans →</a>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-grid">
|
||||
<div class="footer-brand">
|
||||
<h4>📸 SnapAPI</h4>
|
||||
<p>The EU-hosted screenshot API for developers. Convert any URL to a pixel-perfect image with a simple API call.</p>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h5>Product</h5>
|
||||
<a href="/#features">Features</a>
|
||||
<a href="/pricing">Pricing</a>
|
||||
<a href="/docs">API Docs</a>
|
||||
<a href="/blog">Blog</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h5>Developers</h5>
|
||||
<a href="/docs">Swagger / OpenAPI</a>
|
||||
<a href="/guides/quick-start">Quick Start</a>
|
||||
<a href="/changelog">Changelog</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h5>Legal</h5>
|
||||
<a href="/impressum.html">Impressum</a>
|
||||
<a href="/privacy.html">Privacy Policy</a>
|
||||
<a href="/terms.html">Terms of Service</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<span>© 2026 Cloonar Technologies GmbH · FN 631089y · ATU81280034</span>
|
||||
<span>EU-hosted 🇪🇺 · All data stays in Europe</span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
<url><loc>https://snapapi.eu/blog</loc><changefreq>weekly</changefreq><priority>0.8</priority></url>
|
||||
<url><loc>https://snapapi.eu/blog/why-screenshot-api</loc><changefreq>monthly</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://snapapi.eu/blog/screenshot-api-performance</loc><changefreq>monthly</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://snapapi.eu/blog/dark-mode-screenshots</loc><changefreq>monthly</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://snapapi.eu/blog/automating-og-images</loc><changefreq>monthly</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://snapapi.eu/impressum.html</loc><changefreq>yearly</changefreq><priority>0.2</priority></url>
|
||||
<url><loc>https://snapapi.eu/privacy.html</loc><changefreq>yearly</changefreq><priority>0.2</priority></url>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue