Last active
April 13, 2021 19:13
-
-
Save kaicataldo/f28b6adf941d1575afa78e647624a327 to your computer and use it in GitHub Desktop.
Dark mode using Gatsby.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
export const onRenderBody = function({ setPreBodyComponents }) { | |
// Load dark mode script in head to prevent FOUC. | |
setPreBodyComponents([ | |
<script | |
type="text/javascript" | |
key="theme-initializer" | |
src="/set-dark-mode.js" | |
/>, | |
]); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/* | |
* This script isn't bundled by Gatsby and is instead a static asset that is loaded into the head. This allows it to be | |
* cached on future loads. It intentionally is blocking to ensure that there isn't a FOUC when the rest of the | |
* bundle loads and is hydrated. | |
*/ | |
(function() { | |
var mode, colorScheme; | |
try { | |
mode = window.localStorage.getItem('color-scheme-mode') || 'auto'; | |
} catch (err) {} | |
colorScheme = | |
!mode || mode === 'auto' | |
? window.matchMedia('(prefers-color-scheme: dark)').matches | |
? 'dark' | |
: 'light' | |
: mode; | |
document.body.setAttribute('data-color-mode', mode); | |
document.body.setAttribute('data-color-scheme', colorScheme); | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useEffect } from 'react'; | |
export const AUTO_MODE = 'auto'; | |
export const LIGHT_MODE = 'light'; | |
export const DARK_MODE = 'dark'; | |
export const MODES = [AUTO_MODE, LIGHT_MODE, DARK_MODE]; | |
const MATCH_MEDIA_QUERY = `(prefers-color-scheme: ${DARK_MODE})`; | |
function isBrowser() { | |
return typeof window !== 'undefined'; | |
} | |
function getInitialMode() { | |
let mode; | |
try { | |
mode = window.localStorage.getItem('color-scheme-mode'); | |
} catch {} | |
return mode || AUTO_MODE; | |
} | |
function getColorScheme(mode) { | |
return mode === AUTO_MODE | |
? isBrowser() && window.matchMedia(MATCH_MEDIA_QUERY).matches | |
? DARK_MODE | |
: LIGHT_MODE | |
: mode; | |
} | |
export default function useDarkMode() { | |
const [mode, setMode] = useState(getInitialMode()); | |
const [colorScheme, setColorScheme] = useState(getColorScheme(mode)); | |
function cycleMode() { | |
setMode(MODES[(MODES.indexOf(mode) + 1) % 3]); | |
} | |
useEffect(() => { | |
const darkColorSchemeQueryHandler = e => { | |
let colorScheme; | |
try { | |
colorScheme = window.localStorage.getItem('color-scheme-mode'); | |
} catch {} | |
if (colorScheme && colorScheme !== AUTO_MODE) { | |
return; | |
} | |
setColorScheme(e.matches ? DARK_MODE : LIGHT_MODE); | |
}; | |
const darkColorSchemeQuery = window.matchMedia(MATCH_MEDIA_QUERY); | |
darkColorSchemeQuery.addListener(darkColorSchemeQueryHandler); | |
return () => | |
darkColorSchemeQuery.removeListener(darkColorSchemeQueryHandler); | |
}, []); | |
useEffect(() => { | |
try { | |
window.localStorage.setItem('color-scheme-mode', mode); | |
} catch (err) {} | |
document.body.setAttribute('data-color-mode', mode); | |
setColorScheme(getColorScheme(mode)); | |
}, [mode]); | |
useEffect(() => { | |
document.body.setAttribute('data-color-scheme', colorScheme); | |
}, [colorScheme]); | |
return [mode, cycleMode]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is my solution to prevent a FOUC (flash of unstyled content) when implementing a dark mode toggle on my Gatsby site (https://kaicataldo.com).
The
set-dark-mode.js
script referenced ingatsby-ssr.js
gets loaded in the head as a blocking static asset so that it executes before the body is parsed and rendered. I used to inject it directly into the markup, but have since externalized it to allow for the script to be cached on subsequent loads. I'm using CSS variables to change the colors in the styles.Example of what the styles look like:
The hook is used in my main
Layout
component like so:These are both passed to child components so that they can display the correct icon using
mode
as well as cycling the mode (between auto, light, and dark) using thecycleMode
callback.Would love to hear about other possible solutions!