Skip to content

Commit

Permalink
chore: add light/dark mode theme detection to image component example (
Browse files Browse the repository at this point in the history
…#53760)

This PR adds documentation for light/dark mode detection with `next/image`.

In the future, we could also document the picture solution once #51205 goes stable (although some of the preloading would not be possible).

* x-ref: https://twitter.com/victorbayas/status/1688596439704780822
  • Loading branch information
styfle committed Aug 9, 2023
1 parent 6809edc commit 8730183
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 0 deletions.
60 changes: 60 additions & 0 deletions docs/02-app/02-api-reference/01-components/image.mdx
Expand Up @@ -725,6 +725,66 @@ Try it out:

- [Demo the `fill` prop](https://image-component.nextjs.gallery/fill)

## Theme Detection

If you want to display a different image for light and dark mode, you can create a new component that wraps two `<Image>` components and reveals the correct one based on a CSS media query.

```css filename="components/theme-image.module.css"
.imgDark {
display: none;
}

@media (prefers-color-scheme: dark) {
.imgLight {
display: none;
}
.imgDark {
display: unset;
}
}
```

```tsx filename="components/theme-image.tsx" switcher
import styles from './theme-image.module.css'
import Image, { ImageProps } from 'next/image'

type Props = Omit<ImageProps, 'src' | 'priority' | 'loading'> & {
srcLight: string
srcDark: string
}

const ThemeImage = (props: Props) => {
const { srcLight, srcDark, ...rest } = props

return (
<>
<Image {...rest} src={srcLight} className={styles.imgLight} />
<Image {...rest} src={srcDark} className={styles.imgDark} />
</>
)
}
```

```jsx filename="components/theme-image.js" switcher
import styles from './theme-image.module.css'
import Image from 'next/image'

const ThemeImage = (props) => {
const { srcLight, srcDark, ...rest } = props

return (
<>
<Image {...rest} src={srcLight} className={styles.imgLight} />
<Image {...rest} src={srcDark} className={styles.imgDark} />
</>
)
}
```

> **Good to know**: The default behavior of `loading="lazy"` ensures that only the correct image is loaded. You cannot use `priority` or `loading="eager"` because that would cause both images to load. Instead, you can use [`fetchPriority="high"`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority).
- [Demo light/dark mode theme detection](https://image-component.nextjs.gallery/theme)

## Known Browser Bugs

This `next/image` component uses browser native [lazy loading](https://caniuse.com/loading-lazy-attr), which may fallback to eager loading for older browsers before Safari 15.4. When using the blur-up placeholder, older browsers before Safari 12 will fallback to empty placeholder. When using styles with `width`/`height` of `auto`, it is possible to cause [Layout Shift](https://web.dev/cls/) on older browsers before Safari 15 that don't [preserve the aspect ratio](https://caniuse.com/mdn-html_elements_img_aspect_ratio_computed_from_attributes). For more details, see [this MDN video](https://www.youtube.com/watch?v=4-d_SoCHeWE).
Expand Down
3 changes: 3 additions & 0 deletions examples/image-component/pages/index.tsx
Expand Up @@ -50,6 +50,9 @@ const Index = () => (
<li>
<Link href="/color">Color placeholder</Link>
</li>
<li>
<Link href="/theme">Light/Dark mode theme detection</Link>
</li>
<li>
<Link href="/background">Text on background image</Link>
</li>
Expand Down
38 changes: 38 additions & 0 deletions examples/image-component/pages/theme.tsx
@@ -0,0 +1,38 @@
import Image, { ImageProps } from 'next/image'
import ViewSource from '../components/view-source'
import styles from '../styles.module.css'

// Note: we cannot use `priority` or `loading="eager"
// because we depend on the default `loading="lazy"`
// behavior to wait for CSS to reveal the proper image.
type Props = Omit<ImageProps, 'src' | 'priority' | 'loading'> & {
srcLight: string
srcDark: string
}

const ThemeImage = (props: Props) => {
const { srcLight, srcDark, ...rest } = props

return (
<>
<Image {...rest} src={srcLight} className={styles.imgLight} />
<Image {...rest} src={srcDark} className={styles.imgDark} />
</>
)
}

const Page = () => (
<div>
<ViewSource pathname="pages/theme.tsx" />
<h1>Image With Light/Dark Theme Detection</h1>
<ThemeImage
alt="Next.js Streaming"
srcLight="https://assets.vercel.com/image/upload/front/nextjs/streaming-light.png"
srcDark="https://assets.vercel.com/image/upload/front/nextjs/streaming-dark.png"
width={588}
height={387}
/>
</div>
)

export default Page
14 changes: 14 additions & 0 deletions examples/image-component/styles.module.css
Expand Up @@ -48,3 +48,17 @@
padding-top: 40vh;
text-shadow: 1px 1px 1px #3c5c5e;
}

.imgDark {
display: none;
}

@media (prefers-color-scheme: dark) {
.imgLight {
display: none;
}

.imgDark {
display: unset;
}
}

0 comments on commit 8730183

Please sign in to comment.