VueUse Composable Patterns
Quick Guide: VueUse provides 250+ composable functions for Vue 3 that wrap browser APIs, sensors, network, state, and component logic into reactive composables. Import individual functions from@vueuse/corefor tree-shaking. All composables return reactive refs and follow Vue Composition API conventions. UseuseLocalStoragefor persistent state,useFetchfor reactive HTTP,useIntersectionObserverfor visibility detection,createGlobalStatefor shared state, anduseVModelfor two-way binding helpers. Always call composables in<script setup>orsetup()synchronously. Clean up is automatic via Vue's lifecycle.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)
(You MUST call VueUse composables synchronously inside <script setup> or setup() -- NEVER inside async callbacks, setTimeout, or conditional blocks)
(You MUST use shallowRef instead of ref for large objects/arrays in VueUse composables to avoid deep reactivity overhead)
(You MUST handle SSR by checking typeof window !== "undefined" or using VueUse's built-in SSR-safe composables -- browser APIs crash during server rendering)
**(You MUST import individual composables from @vueuse/core -- NEVER use import * as vueuse which defeats tree-shaking)**
</critical_requirements>
Auto-detection: VueUse, vueuse, @vueuse/core, @vueuse/integrations, useLocalStorage, useSessionStorage, useClipboard, useFetch, useMouse, useScroll, useIntersectionObserver, useResizeObserver, useElementVisibility, useMediaQuery, usePreferredDark, useDark, useToggle, useCounter, useCycleList, useWebSocket, useEventSource, useEventListener, useTransition, useRafFn, createGlobalState, useRefHistory, syncRef, useVModel, useVirtualList, onClickOutside, onKeyStroke, createFetch
When to use:
- Browser API access with reactive wrappers (localStorage, clipboard, media queries)
- Sensor data as reactive refs (mouse position, scroll, resize, intersection)
- Reactive HTTP requests with abort, refetch, and interceptors
- Global state shared across components without a state management library
- Component utilities (v-model helpers, virtual lists, keyboard shortcuts)
- SSR-safe browser API access
When NOT to use:
- Complex state management with actions/mutations (use a dedicated state solution)
- Server-side data fetching with caching/invalidation (use your data-fetching solution)
- Simple one-off operations that don't need reactivity
- Non-Vue projects (VueUse depends on Vue 3's reactivity system)
Key patterns covered:
- Core utilities (
useToggle,useCounter,useCycleList) - Browser composables (
useLocalStorage,useClipboard,useMediaQuery,useEventListener) - Sensor composables (
useMouse,useScroll,useIntersectionObserver,useResizeObserver) - Network composables (
useFetch,useWebSocket,useEventSource) - State composables (
createGlobalState,useRefHistory,syncRef) - Component composables (
useVModel,useVirtualList,onClickOutside,onKeyStroke) - Animation composables (
useTransition,useRafFn)
Detailed Resources:
- [examples/core.md](examples/core.md) - Core utilities, browser APIs, event listeners
- [examples/sensors.md](examples/sensors.md) - Mouse, scroll, intersection, resize, visibility
- [examples/network.md](examples/network.md) - useFetch, useWebSocket, useEventSource
- [examples/state.md](examples/state.md) - createGlobalState, useRefHistory, syncRef, persistence
- [examples/component.md](examples/component.md) - useVModel, useVirtualList, onClickOutside, onKeyStroke
- [reference.md](reference.md) - Composable decision framework, SSR guide, gotchas
<philosophy>
Philosophy
VueUse follows the Composition API composable pattern: encapsulate reactive logic in reusable functions that return refs and methods. Each composable wraps a single concern (a browser API, sensor, or utility) and handles setup/teardown automatically via Vue's lifecycle.
Key principle: VueUse composables are designed for tree-shaking. Import only what you use -- each function is independently importable with minimal overhead.
import { useLocalStorage, useDark, useMediaQuery } from "@vueuse/core";
// Reactive localStorage -- syncs across tabs
const userName = useLocalStorage("user-name", "Guest");
// Dark mode with auto-detection
const isDark = useDark();
// Responsive breakpoint
const MOBILE_BREAKPOINT = "(max-width: 768px)";
const isMobile = useMediaQuery(MOBILE_BREAKPOINT);
When to Use VueUse
- Wrapping browser APIs with reactivity (localStorage, clipboard, geolocation)
- Tracking DOM state reactively (scroll position, element visibility, mouse)
- Lightweight global state without full store setup
- Component utilities (keyboard shortcuts, click outside, virtual lists)
- Reactive data fetching for simple cases
When NOT to Use VueUse
- Complex server state with caching, invalidation, optimistic updates (use your data-fetching solution)
- Complex client state with computed derivations and devtools (use a dedicated state solution)
- Non-Vue projects (the reactivity system is Vue-specific)
- When native browser API is sufficient without reactivity
Current Version
VueUse v14.x is the current stable release, supporting Vue 3.5+. All composables are written in TypeScript with full type inference.
</philosophy>
<patterns>
Core Patterns
Pattern 1: Reactive Storage (useLocalStorage, useSessionStorage)
Persistent reactive state that syncs with browser storage and across tabs.
<script setup lang="ts">
import { useLocalStorage, useSessionStorage } from "@vueuse/core";
interface UserPreferences {
theme: "light" | "dark";
fontSize: number;
sidebarCollapsed: boolean;
}
const DEFAULT_PREFERENCES: UserPreferences = {
theme: "light",
fontSize: 14,
sidebarCollapsed: false,
};
// ✅ Good Example - Typed reactive localStorage
const preferences = useLocalStorage<UserPreferences>(
"user-prefs",
DEFAULT_PREFERENCES,
);
// Read and write like a normal ref
preferences.value.theme = "dark"; // auto-persists to localStorage
// Session-only storage (cleared on tab close)
const sessionToken = useSessionStorage("session-token", "");
// Remove from storage
preferences.value = null; // removes the key from localStorage
</script>
Why good: automatic serialization/deserialization, cross-tab sync via storage events, typed with generics, cleanup is automatic
// ❌ Bad Example - Manual localStorage
const prefs = ref(JSON.parse(localStorage.getItem("user-prefs") ?? "null"));
watch(
prefs,
(val) => {
localStorage.setItem("user-prefs", JSON.stringify(val)); // manual sync
},
{ deep: true },
);
// No cross-tab sync, no automatic cleanup, error-prone serialization
Why bad: manual serialization is error-prone, no cross-tab sync, no SSR safety, deep watcher on every change is expensive
Pattern 2: Event Listeners (useEventListener)
Auto-cleaned event listeners with type safety.
<script setup lang="ts">
import { useEventListener } from "@vueuse/core";
import { useTemplateRef } from "vue";
const buttonRef = useTemplateRef("button");
// ✅ Good Example - Auto-cleanup event listeners
// Automatically removed when component unmounts
useEventListener(window, "resize", () => {
console.log("Window resized:", window.innerWidth);
});
// Works with template refs -- waits for mount
useEventListener(buttonRef, "click", (event) => {
console.log("Button clicked:", event);
});
// Multiple events on same target
useEventListener(document, "visibilitychange", () => {
console.log("Visibility:", document.visibilityState);
});
</script>
Why good: automatic cleanup on unmount prevents memory leaks, handles ref lifecycle (waits for