Search code examples
imagenext.jsmedia-queriesdarkmodenextjs-image

How can I present a different next/Image based on the user's preferred color scheme?


I'm trying to use a next/image instead of a regular img tag in the code below. With the regular <img> tag, the following achieves exactly what I'm looking for:

<div>
  <picture>
    <source
      srcSet="https://via.placeholder.com/100/333333/ffffff.png"
      media="(prefers-color-scheme: dark)"
    />
    <img
      src='https://via.placeholder.com/100/dddddd/000000.png'
      width='100px'
      height='100px'
      alt='Placeholder image'
    />
  </picture>
  <p>Change your OS or browser's preferred color scheme to see a different image.</p>
</div>

Indeed, when I set my OS (or browser) to the dark theme, I get a dark image, and vice-versa for the light theme.

However, if I try the same thing with a next/image, I just get the light-themed image every time… I can't put this into a snippet because next/image requires a Next.js server, but here is the code that I'm using, which, in my tests, is backed by a Next.js development server with the appropriate image-related settings configured in next.config.js:

// pages/test.js
import Image from 'next/image'

export default function MyWebPage () {
  return (
    <div>
      <picture>
        <source
          srcSet="https://via.placeholder.com/100/333333/ffffff.png"
          media="(prefers-color-scheme: dark)"
        />
        <Image src='https://via.placeholder.com/100/dddddd/000000.png' width='100px' height='100px' alt='Placeholder image' />
      </picture>
      <p>You can change your OS or browser's preferred color scheme, but you'll always see the light-theme image.</p>
    </div>
  )
}

Here I never get the dark-themed image, unfortunately.

Theories:

  • Perhaps next/image doesn't interact with the <picture> tag exactly the same way as <img>? But I'm not finding anything online about using next/image with the <picture> tag…
  • Perhaps I should be providing this media-query-dependant source set in a different way when using next/image? But, I'm not finding any media attribute in the next/image docs…

Question: How can I change the src of my next/image based on the user's preferred color scheme?


Non-solutions:

  • I could put 2 images on the page and use display: none on one of the two as a function of the user's preferred color scheme, but I'm hoping to find a solution that doesn't require so many duplicate images all over the place, which incurs a (small) performance penalty and makes the code that much harder to maintain, or that much more complex if a helper component is created.
  • I could change the src using Javascript when the page loads, but this would result in a flash of incorrectly styled content and generally does against my objective of having my page fully server-rendered and compatible with browsers where Javascript is turned off.
  • I could use cookies to let the server know about a user's color scheme preference and render the page consequently, but this would not work for the very first visit and comes with the requirement to include a cookie bar to inform the user of the reasons behind the use of cookies, as well as a way to opt-out.

Solution

  • A bit late to the party, but had no issues using this within NextJs 13.

    // Relative import to your image file
    import MyLightImage from '../../../public/my-light-image.png';
    import MyDarkImage from '../../../public/my-dark-image.png';
    
    const MyImage = () => {
        return (
          <picture>
            <source srcSet={MyDarkImage.src} media="(prefers-color-scheme: dark)" />
            <Image
                src={MyLightImage}
                alt="My image"
                width={300}
                height={300}
            />
          </picture>
      );
    };
    
    export default MyImage;
    

    This would display MyImage for light theme and MyDarkImage for dark theme.