VueUse is a collection of composables for common tasks: browser APIs, sensors, state, animations, network, and more. Instead of writing your own useLocalStorage or useDebounceFn from scratch, you install @vueuse/core and get 200+ battle-tested composables that work with Vue 3's reactivity system.
npm install @vueuse/coreMost useful composables by category
Browser and DOM
useLocalStorage / useSessionStorage: reactive storage that syncs automatically.
const theme = useLocalStorage('theme', 'light')
theme.value = 'dark' // saved to localStorage immediatelyuseClipboard: copy to clipboard.
const { copy, copied } = useClipboard()
await copy('Hello!')
// copied.value is true for 1.5 secondsuseMediaQuery: reactive CSS media query.
const isMobile = useMediaQuery('(max-width: 768px)')useDark: dark mode with persistence.
const isDark = useDark()
const toggle = useToggle(isDark)useEventListener: auto-cleaned event listeners.
useEventListener(window, 'resize', () => {
console.log(window.innerWidth)
})
// listener removed automatically when component unmountsState
useToggle: boolean toggle.
const [value, toggle] = useToggle(false)
toggle() // true
toggle() // falseuseDebounceFn / useThrottleFn: debounce and throttle.
const search = useDebounceFn((query: string) => {
fetchResults(query)
}, 300)createGlobalState: shared state across components without Pinia.
const useGlobalCounter = createGlobalState(() => {
const count = ref(0)
return { count }
})Network
useFetch: reactive fetch wrapper (different from Nuxt's useFetch).
const { data, error, isFetching } = useFetch('https://api.example.com/posts')
.get()
.json<Post[]>()useWebSocket: reactive WebSocket connection.
const { data, send, status } = useWebSocket('wss://example.com/ws')
watch(data, (message) => {
console.log('Received:', message)
})Sensors
useMouse: reactive mouse position.
const { x, y } = useMouse()useIntersectionObserver: detect element visibility.
const target = ref<HTMLElement>()
const isVisible = ref(false)
useIntersectionObserver(target, ([entry]) => {
isVisible.value = entry.isIntersecting
})useElementSize: reactive element dimensions.
const el = ref<HTMLElement>()
const { width, height } = useElementSize(el)Utilities
watchDebounced: debounced watcher.
const search = ref('')
watchDebounced(search, (value) => {
fetchResults(value)
}, { debounce: 300 })whenever: watch that fires only when value is truthy.
const isReady = ref(false)
whenever(isReady, () => {
console.log('Ready!')
})useAsyncState: run async function with reactive loading/error state.
const { state, isLoading, error } = useAsyncState(
() => fetch('/api/user').then(r => r.json()),
null // initial state
)Using in a real component
<script setup>
import { useLocalStorage, useDebounceFn, useMediaQuery } from '@vueuse/core'
const searchQuery = useLocalStorage('search', '')
const isMobile = useMediaQuery('(max-width: 768px)')
const debouncedSearch = useDebounceFn((query: string) => {
fetchResults(query)
}, 300)
watch(searchQuery, (q) => debouncedSearch(q))
</script>
<template>
<input v-model="searchQuery" :placeholder="isMobile ? 'Search...' : 'Search articles...'" />
</template>VueUse vs writing your own
Write your own composable when the logic is specific to your domain. Use VueUse when the problem is generic (debounce, storage, media queries, clipboard, intersection observer). VueUse composables handle edge cases, SSR compatibility, and cleanup that you would otherwise have to implement yourself.