Images that adapt to light/dark mode

A simple way to display images based on the device or site theme

17 days ago
2 min read329 words

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.