4

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.
Shawn
  • 10,931
  • 18
  • 81
  • 126
  • 1
    This is a use case that `next/image` isn't suited for yet. I'd suggest you either stick with `` + `` elements, or use the 2 images solution if you really want to use `next/image`. – juliomalves Mar 13 '22 at 12:44
  • Yet? Are you saying this might be in the plans for the future? – Shawn Mar 13 '22 at 23:41
  • 1
    I don't actually know. I'd just expect something like this would come at some point to `next/image`, as it's seems quite a useful/common use case. – juliomalves Mar 13 '22 at 23:46

4 Answers4

8

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.

Mentlegen
  • 988
  • 8
  • 11
  • Can confirm! The dark image looks broken when switching between dark and light while simulating being offline by killing the dev server (`next dev`) but this isn't a problem with the actual production server (`next start`). – Shawn Feb 27 '23 at 22:36
2

This issue can be solved by using a conditional statement to switch between the images based on the theme with "useTheme" from "next-themes".

import Image from "next/image";
import { useTheme } from "next-themes";

interface MyImageProps{
  DefaultImage: string;
  DarkImage: string;
}

const MyImage: React.FC<MyImageProps> = ({DefaultImage, DarkImage}) => {

const { theme } = useTheme();

  let imageUrl = DefaultImage;
  if (theme === "dark") {
    imageUrl = DarkImage;
  }

    return (
      <Image
          alt="image"
          src={imageUrl}
          width={217}
          height={164}
          sizes="100vw"
        />
  );
};

export default MyImage;

In this example, a static data was used as the source of the images.

"use client";
import MyImage from '../path of MyImage'


const ImagesData = [
  DefaultImage:'path to the image',
  DarkImage: 'path to the image'
]

const ImageSwitcher = () => {
  return(
   <>
      {ImagesData.map((item)=>(
         <MyImage
          key={item.DarkImage}
          DarkImage={item.DarkImage}
          DefaultImage={item.DefaultImage}
         />
     ))}
  </>
  )
}
King
  • 21
  • 4
0

One aspect that prevented the solutions provided above for me to work were the OS dark/light mode settings. I wanted to use a dark/light mode toggle, but did not realize that the changes only worked with the OS dark/light mode changes.

See the solution here (I adapted it from TS to JS).

Otherwise the media preference will only respond to the OS-level setting

bons.ai
  • 31
  • 1
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 23 '23 at 06:27
0

if you are using tailwindcss, you can achieve by adding hidden utility to Image class

 <div>
        <Image
          className="hidden dark:block"
          src={myDarkModeImage}
          alt="dark-mode-image"
          width={40}
          height={40}
        />
        <Image
          className="mb-4 block dark:hidden"
          src={myLightModeImage}
          alt="light-mode-image"
          width={40}
          height={40}
        />
  </div>
Suhas C V
  • 104
  • 1
  • 5