home / skills / serkodev / vue-skills / create-agnostic-composable
This skill helps you build agnostic Vue composables that accept plain values, refs, or getters and normalize inputs for predictable reactivity.
npx playbooks add skill serkodev/vue-skills --skill create-agnostic-composableReview the files below or copy the command above to add this skill to your agents.
---
name: create-agnostic-composable
description: Create a library-grade Vue composable that accepts maybe-reactive inputs (MaybeRef / MaybeRefOrGetter) so callers can pass a plain value, ref, or getter. Normalize inputs with toValue()/toRef() inside reactive effects (watch/watchEffect) to keep behavior predictable and reactive. Use this skill when user asks for creating agnostic or reusable composables.
license: MIT
metadata:
author: SerKo <https://github.com/serkodev>
version: "0.1"
compatibility: Requires Vue 3 (or above) or Nuxt 3 (or above) project
---
# Create Agnostic Composable
Agnostic composables are reusable functions that can accept both reactive and non-reactive inputs. This allows developers to use the composable in a variety of contexts without worrying about the reactivity of the inputs.
Steps to design an agnostic composable in Vue.js:
1. Confirm the composable's purpose and API design and expected inputs/outputs.
2. Identify inputs params that should be reactive (MaybeRef / MaybeRefOrGetter).
3. Use `toValue()` or `toRef()` to normalize inputs inside reactive effects.
4. Implement the core logic of the composable using Vue's reactivity APIs.
## Core Type Concepts
### Type Utilities
```ts
/**
* value or writable ref (value/ref/shallowRef/writable computed)
*/
export type MaybeRef<T = any> = T | Ref<T> | ShallowRef<T> | WritableComputedRef<T>;
/**
* MaybeRef<T> + ComputedRef<T> + () => T
*/
export type MaybeRefOrGetter<T = any> = MaybeRef<T> | ComputedRef<T> | (() => T);
```
### Policy and Rules
- Read-only, computed-friendly input: use `MaybeRefOrGetter`
- Needs to be writable / two-way input: use `MaybeRef`
- Parameter might be a function value (callback/predicate/comparator): do not use `MaybeRefOrGetter`, or you may accidentally invoke it as a getter.
- DOM/Element targets: if you want computed/derived targets, use `MaybeRefOrGetter`.
When `MaybeRefOrGetter` or `MaybeRef` is used:
- resolve reactive value using `toRef()` (e.g. watcher source)
- resolve non-reactive value using `toValue()`
### Examples
Agnostic `useDocumentTitle` Composable: read-only title parameter
```ts
import { watch, toRef } from 'vue'
import type { MaybeRefOrGetter } from 'vue'
export function useDocumentTitle(title: MaybeRefOrGetter<string>) {
watch(toRef(title), (t) => {
document.title = t
}, { immediate: true })
}
```
Agnostic `useCounter` Composable: two-way writable count parameter
```ts
import { watch, toRef } from 'vue'
import type { MaybeRef } from 'vue'
function useCounter(count: MaybeRef<number>) {
const countRef = toRef(count)
function add() {
countRef.value++
}
return { add }
}
```
This skill teaches how to create library-grade Vue composables that accept maybe-reactive inputs (MaybeRef / MaybeRefOrGetter). It explains when to use toRef() and toValue() inside reactive effects so callers can pass plain values, refs, or getter functions. The goal is predictable, reusable composables that behave correctly in both reactive and non-reactive contexts.
Design the composable API first and mark parameters that should accept reactive or non-reactive inputs. Inside reactive effects (watch, watchEffect), normalize inputs with toRef() for watcher sources and toValue() when you need a snapshot. Choose MaybeRef for writable two-way inputs and MaybeRefOrGetter for read-only or computed-friendly inputs. Implement core logic using Vue reactivity primitives so the composable remains agnostic to caller input type.
When should I use toRef() vs toValue()?
Use toRef() when you need a reactive Ref source inside watch/watchEffect (so updates propagate). Use toValue() when you only need a current snapshot and do not need reactivity from a non-ref input.
What if a parameter can be a function callback?
Do not mark callback parameters as MaybeRefOrGetter unless the function is intentionally a getter. Treat callbacks as plain functions to avoid accidental invocation during normalization.