Images that adapt to light/dark mode
A simple way to display images based on the device or site theme
I recently came across a simple way to display images based on the device or site theme without JavaScript.
The problem
When building a landing page, I had two versions of a dashboard screenshot: one for light mode and one for dark mode. I needed to show the right one depending on the active theme.
My first instinct was to reach for a useTheme hook from next-themes and conditionally render the image in a client component. But that approach comes with downsides:
- Requires marking the component as
"use client" - Can cause a hydration flash; the wrong image briefly appears before JS runs
- More code for a simple visual concern
The CSS-only solution
Tailwind's dark: variant makes this trivial. Render both images and toggle their visibility with utility classes:
<Image
src="/img/dashboard-light.jpeg"
alt="Dashboard"
width={1200}
height={630}
className="w-full object-cover dark:hidden"
/>
<Image
src="/img/dashboard-dark.jpeg"
alt="Dashboard"
width={1200}
height={630}
className="hidden w-full object-cover dark:block"
/>
How it works
| Class | Behaviour |
|---|---|
| (no class) | Visible by default |
dark:hidden |
Hidden when dark mode is active |
hidden |
Hidden by default |
dark:block |
Visible when dark mode is active |
So the light image is always visible until dark mode kicks in and hides it. The dark image is always hidden until dark mode makes it visible.
A common mistake
The subtle bug that caused both images to show at the same time was forgetting hidden on the dark image.
{/* ❌ Wrong: dark image is visible in light mode too */}
className="w-full object-cover dark:block"
{/* ✅ Correct: hidden by default, revealed in dark mode */}
className="hidden w-full object-cover dark:block"
Why this is better than useTheme
| CSS classes | useTheme hook |
|
|---|---|---|
| Requires client component | No | Yes |
| Hydration flash risk | None | Possible |
| Works without JavaScript | Yes | No |
| Code complexity | Minimal | More |
The only tradeoff is that both images are downloaded by the browser. For large images where bandwidth matters, a useTheme approach (or the native <picture> element with prefers-color-scheme) may be worth it. For most cases, the CSS approach is the right default.
Using the native <picture> element
If you're not using Tailwind or need the browser to only fetch the relevant image, the HTML <picture> element handles this natively:
<picture>
<source srcset="/img/dashboard-dark.jpeg" media="(prefers-color-scheme: dark)" />
<img src="/img/dashboard-light.jpeg" alt="Dashboard" />
</picture>
The caveat: this responds to the OS preference, not your app's theme toggle. If your site has its own dark mode switch independent of the system setting, the CSS class approach stays in sync automatically.