Dark mode in WebGL
Use matchMedia
and listen for changes to get updates when dark mode is toggled.
import { useEffect, useState } from "react"
export const useDarkMode = () => {
const [isDarkMode, setIsDarkMode] = useState(
window.matchMedia('(prefers-color-scheme: dark)').matches
)
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = (event: MediaQueryListEvent) => {
setIsDarkMode(event.matches)
}
mediaQuery.addEventListener('change', handleChange)
return () => {
mediaQuery.removeEventListener('change', handleChange)
}
}, [])
return isDarkMode
}
const ExampleOne = () => {
const darkMode = useDarkMode()
return (
<Canvas>
<TorusKnot color={darkMode ? 'cyan' : 'hotpink'} />
</Canvas>
)
}
CSS Custom Properties are a powerful tool and are easy to consume in CSS, but it gets a bit trickier when you want to use them outside of CSS. We can create another hook which will extract custom properties and update when dark mode is toggled
const extractCustomProperties = (element: Element, properties: string[]) => {
const style = global.getComputedStyle(element)
return properties.reduce((previous, current) => {
previous[current] = style.getPropertyValue(current) || null
return previous
}, {} as Record<string, string | null>)
}
export const useCustomProperties = <K extends string>(
properties: K[]
): {
ref: React.Ref<HTMLElement>
customProperties: Record<K, string | null>
CustomProperties: React.FC<React.HTMLAttributes<HTMLDivElement>>
} => {
const darkMode = useDarkMode()
const elRef = useRef<HTMLElement>(null)
const [customProperties, setCustomProperties] = useState(
{} as Record<K, string | null>
)
useEffect(() => {
if (!elRef.current) return
setCustomProperties(
extractCustomProperties(elRef.current, properties) as Record<
K,
string | null
>
)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [darkMode, ...properties])
const CustomProperties = ({
children,
...restProps
}: React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>) => (
<div ref={elRef as React.Ref<HTMLDivElement>} {...restProps}>
{children}
</div>
)
return useMemo(
() => ({
ref: elRef,
customProperties,
CustomProperties,
}),
[customProperties]
)
}
const ExampleTwo = () => {
const { CustomProperties, customProperties } = useCustomProperties(['--🎨-art-accent'])
const color = customProperties['--🎨-art-accent'] || 'green'
return (
<CustomProperties>
<Canvas>
<TorusKnot color={color} />
</Canvas>
</CustomProperties>
)
}