How vueuse Supports Vue 2 & Vue 3 via vue‑demi and Implements Dark Mode
This article explains how the vueuse utility library works with both Vue 2 and Vue 3 by leveraging the vue‑demi compatibility layer, and then dives into the implementation of its useDark hook, showing the underlying media‑query logic and CSS techniques for dark‑mode support.
vueuseis a popular utility library built on Vue's Composition API. It works with both Vue 2 and Vue 3 by using the compatibility package vue-demi, which dynamically switches the underlying Vue version at install time.
The core of vue-demi is its postinstall script defined in package.json:
<code style="padding: 16px; color: black; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">"scripts": {
"postinstall": "node -e \"try{require('./scripts/postinstall.js')}catch(e){}\"",
},
</code>During post‑install, the script detects the installed Vue version and rewrites the entry files in package.json to point to the appropriate Vue API implementation. The key function switchVersion copies version‑specific source files ( index.cjs, index.mjs, index.d.ts) into the library root and, for Vue 2, updates the Composition API exports.
<code style="padding: 16px; color: black; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">const { switchVersion, loadModule } = require('./utils')
const Vue = loadModule('vue')
if (!Vue || typeof Vue.version !== 'string') {
console.warn('[vue-demi] Vue is not found. Please run "npm install vue" to install.')
} else if (Vue.version.startsWith('2.7.')) {
switchVersion(2.7)
} else if (Vue.version.startsWith('2.')) {
switchVersion(2)
} else if (Vue.version.startsWith('3.')) {
switchVersion(3)
} else {
console.warn(`[vue-demi] Vue version v${Vue.version} is not supported.`)
}
</code>Because vue-demi simply re‑exports the native Vue 3 API (or a thin wrapper for Vue 2), developers can import hooks from vueuse without worrying about the underlying version:
<code style="padding: 16px; color: black; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">import {
watch,
computed,
Ref,
ref,
set,
del,
nextTick,
isVue2,
} from 'vue-demi'
</code>Beyond version compatibility, the article examines the useDark hook provided by vueuse. The hook builds on usePreferredDark, which itself is a thin wrapper around useMediaQuery('(prefers-color-scheme: dark)'). This media query leverages the browser's matchMedia API to detect whether the user prefers a dark color scheme.
<code style="padding: 16px; color: black; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">export function usePreferredDark(options?: ConfigurableWindow) {
return useMediaQuery('(prefers-color-scheme: dark)', options)
}
</code>The useMediaQuery implementation creates a MediaQueryList, registers change listeners, and returns a reactive ref that reflects the current match state:
<code style="padding: 16px; color: black; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">export function useMediaQuery(query, options = {}) {
const { window = defaultWindow } = options
const isSupported = useSupported(() => window && 'matchMedia' in window && typeof window.matchMedia === 'function')
let mediaQuery
const matches = ref(false)
const handler = (event) => { matches.value = event.matches }
const cleanup = () => {
if (!mediaQuery) return
if ('removeEventListener' in mediaQuery) mediaQuery.removeEventListener('change', handler)
else mediaQuery.removeListener(handler)
}
const stopWatch = watchEffect(() => {
if (!isSupported.value) return
cleanup()
mediaQuery = window.matchMedia(toValue(query))
if ('addEventListener' in mediaQuery) mediaQuery.addEventListener('change', handler)
else mediaQuery.addListener(handler)
matches.value = mediaQuery.matches
})
tryOnScopeDispose(() => { stopWatch(); cleanup(); mediaQuery = undefined })
return matches
}
</code>The useDark hook combines this detection with a useColorMode manager, allowing developers to read or toggle the dark mode state and automatically sync it with the system preference.
<code style="padding: 16px; color: black; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">export function useDark(options = {}) {
const { valueDark = 'dark', valueLight = '', window = defaultWindow } = options
const mode = useColorMode({
...options,
modes: { dark: valueDark, light: valueLight },
onChanged: (mode, defaultHandler) => {
if (options.onChanged) options.onChanged?.(mode === 'dark', defaultHandler, mode)
else defaultHandler(mode)
},
})
const system = computed(() => {
if (mode.system) return mode.system.value
const preferredDark = usePreferredDark({ window })
return preferredDark.value ? 'dark' : 'light'
})
const isDark = computed({
get: () => mode.value === 'dark',
set: (v) => {
const modeVal = v ? 'dark' : 'light'
mode.value = system.value === modeVal ? 'auto' : modeVal
},
})
return isDark
}
</code>In practice, the hook is used like this:
<code style="padding: 16px; color: black; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">import { useDark, useToggle } from '@vueuse/core'
const isDark = useDark()
const toggleDark = useToggle(isDark)
</code>The article also mentions the CSS color-scheme property, which ensures that UI elements such as scrollbars adapt to the selected theme, and shows a simple CSS variable approach for swapping background and text colors between light and dark modes.
<code style="padding: 16px; color: black; display: -webkit-box; font-family: Operator Mono, Consolas, Monaco, Menlo, monospace; font-size: 12px">:root {
--vp-c-bg: #ffffff;
--vp-c-text-1: rgba(60, 60, 67);
}
.dark {
--vp-c-bg: #1b1b1f;
--vp-c-text-1: rgba(255, 255, 245, .86);
}
html.dark { color-scheme: dark; }
</code>Finally, a complete Vue component example demonstrates toggling dark mode with a button, applying the .dark class to the html element, and using the defined CSS variables to change the page appearance.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
