In windicss, there are two ways to implement dark mode adaptation: media queries and classes. And the convenient thing is that the dark
variant provided by windicss will automatically generate corresponding code based on the selected adaptation mode, which can effectively avoid writing a bunch of useless CSS and make it look clearer.
For easy control, we choose to use classes to switch to dark mode (i.e., assign the class name dark
to the root element).
Basic Styles#
First, we need some global CSS to solve the styles that windicss cannot override.
Scrollbar Color Change#
Normally, you might want to use the -webkit-scrollbar
pseudo-class, but there is actually a more elegant way. The browser provides a color-scheme
CSS property, set it to dark, and the browser will automatically render all browser-native elements in a dark style.
html.dark {
color-scheme: dark;
}
Image Brightness Reduction#
It's also simple, just apply a filter.
html.dark img {
filter: brightness(0.8);
}
Automatic Detection#
Next is the highlight, how to determine and add the dark
class name to the HTML element, because windicss won't automatically handle it for us.
We will provide a dropdown box for users on the frontend, where users can choose automatic adaptation, keep dark mode, or keep light mode.
To avoid the flash caused by style switching when the page is initially loaded, we finally decided to store this configuration in cookies instead of local storage. This way, we can make use of SSR. When the user forces dark/light mode, the server can write the class name into the HTML tag.
function readDarkModeInStorage() {
const darkMode = useCookie('darkMode')
const possibleValues = ['auto', 'dark', 'light']
if (darkMode.value && possibleValues.includes(darkMode.value)) {
return darkMode.value
} else {
return 'auto'
}
}
The above is a helper function used to read the dark mode configuration from storage (both server-side and client-side).
function setModeClass(isDark: boolean): void {
if (isDark) {
useHead({
htmlAttrs: { class: 'dark' },
meta: [{ name: 'theme-color', content: '#121212' }],
})
} else {
useHead({
htmlAttrs: { class: '' },
meta: [{ name: 'theme-color', content: '#ffffff' }],
})
}
}
A utility function used to set the dark mode styles. When a boolean value is passed in, it will set the class name of the HTML and the meta tag of the theme-color (both SSR and CSR can be used).
It uses the useHead
method from VueUse.
const currentMode = ref(readDarkModeInStorage())
const preferredDark = usePreferredDark()
watchEffect(() => {
if (currentMode.value === 'auto') {
if (preferredDark.value) {
setModeClass(true)
} else {
setModeClass(false)
}
} else if (currentMode.value === 'dark') {
setModeClass(true)
} else if (currentMode.value === 'light') {
setModeClass(false)
}
useCookie('darkMode').value = currentMode.value
})
This is the key part. First, read the configuration and initialize it to the currentMode
variable. Then, use the usePreferredDark
provided by VueUse to get the current color mode of the browser.
Use a watcher effect function. When the above two values change, call the setModeClass
utility function to modify the class name, and write the configuration to the cookie.