On this page
Composables
Composables are functions that encapsulate reactive state and logic for reuse across components. They follow the use naming convention.
Why Composables?
- Share logic without mixins or inheritance
- Keep components focused on UI
- Test business logic independently
- Compose small functions into larger behaviors
Basic Composable
composables/useCounter.js:
import { ref, computed } from 'vue';
export function useCounter(initial = 0) {
const count = ref(initial);
const double = computed(() => count.value * 2);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
function reset() {
count.value = initial;
}
return { count, double, increment, decrement, reset };
}
Use in a component:
<script setup>
import { useCounter } from '../composables/useCounter';
const { count, double, increment, decrement } = useCounter(10);
</script>
<template>
<p>{{ count }} (×2 = {{ double }})</p>
<button @click="increment">+</button>
<button @click="decrement">−</button>
</template>
useLocalStorage Composable
import { ref, watch } from 'vue';
export function useLocalStorage(key, defaultValue) {
const stored = localStorage.getItem(key);
const value = ref(stored ? JSON.parse(stored) : defaultValue);
watch(value, (newVal) => {
localStorage.setItem(key, JSON.stringify(newVal));
}, { deep: true });
return value;
}
<script setup>
import { useLocalStorage } from '../composables/useLocalStorage';
const theme = useLocalStorage('theme', 'light');
</script>
useMousePosition Composable
import { ref, onMounted, onUnmounted } from 'vue';
export function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(event) {
x.value = event.clientX;
y.value = event.clientY;
}
onMounted(() => window.addEventListener('mousemove', update));
onUnmounted(() => window.removeEventListener('mousemove', update));
return { x, y };
}
Composing Composables
Combine smaller composables into larger ones:
import { computed } from 'vue';
import { useMousePosition } from './useMousePosition';
export function useDistanceFromCenter() {
const { x, y } = useMousePosition();
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
const distance = computed(() =>
Math.hypot(x.value - centerX, y.value - centerY)
);
return { x, y, distance };
}
Conventions
- Name with
use—useFetch,useAuth,useToggle - Return refs — so destructuring stays reactive (or document when to use
storeToRefs-style patterns) - Call at top level — only invoke composables inside
setupor other composables - Cleanup in onUnmounted — remove listeners, abort requests
Use composables for logic; use Pinia when multiple unrelated components need the same global state.
Next: fetch data from APIs with async patterns.