← Back to Design Systems and Frontend Team

Skill · Version 1.0.0

Reusable Vue behavior

Extracts repeated reactive behavior into focused functions with clear inputs, explicit actions and automatic resource cleanup.

vue-composables

Team members using this skill

See how it helps

When is it useful?

Use when several Vue components duplicate fetching, keyboard handling or subscriptions, making fixes difficult to apply consistently.

What does it help you do?

  • Checks existing utilities before extracting a new composable with one responsibility.
  • Accepts reactive inputs and exposes understandable state and action contracts.
  • Releases listeners, timers and pending requests when their owner disappears, including server-rendering considerations.

Try asking this

Extract our repeated search logic into a composable. It should accept changing filters, expose loading and refresh actions, and cancel its request when the component closes. Use the composable in two independent search panels. Closing one must cancel only its request, while changing reactive filters starts a fresh query without stale results.

Detailed documentation

Read the skill’s original instructions, examples and checks in their authored language.

Details…Show less

Composable Design

Build focused, composable use* functions that hide real stateful or side-effect complexity behind a small, predictable TypeScript interface.

1. When to write a composable (and when not to)

  • Check existing project composables and @vueuse/core (if installed) before writing a new one.
  • Extract a composable when stateful reactive logic or side effects (event listeners, observers, async resource lifecycles, keyboard shortcuts, synced storage) are reused across components or obscure a component's main flow.
  • Keep stateless, pure transformations in plain TypeScript utility functions (formatCurrency, parseQuery), not use* composables.
  • Give each composable one clear responsibility (useDocumentTitle, useDebouncedSearch, usePagination) instead of a monolithic usePageEverything.

2. Flexible inputs with MaybeRefOrGetter

  • Accept reactive and plain inputs seamlessly using Vue 3.3+ types:
    • Read-only inputs: type as MaybeRefOrGetter<T> and normalize inside computed, watch or effects with toValue(input).
    • Mutable ref inputs: type as MaybeRef<T> and normalize with toRef(input).
  • Take 1–2 required positional arguments and group optional configuration into a typed options?: UseFeatureOptions object with sensible defaults.

3. Return contract and encapsulation

  • Return a plain object of refs and functions (return { items: readonly(items), isLoading: readonly(isLoading), refresh }) so callers can destructure properties without losing reactivity, while still allowing reactive wrapping if needed.
  • Wrap internal state refs in readonly() when external callers should only mutate state through the composable's returned actions.
  • Make side effects and async triggers explicit: document or configure via immediate?: boolean whether an async composable fetches on creation or waits for an explicit action call.

4. Lifecycle, cleanup and SSR safety

  • Call onMounted, onUnmounted, onScopeDispose and inject synchronously at the top of the composable before any await.
  • Use getCurrentScope() + onScopeDispose(cleanup) (or onUnmounted) to release timers, AbortControllers, DOM listeners and subscriptions automatically when the owning component or effectScope stops.
  • Guard browser-only globals (window, document, localStorage, matchMedia) behind onMounted or typeof window !== 'undefined' checks so the composable never crashes during SSR or unit tests.
  • Never store per-request mutable state at module scope unless the composable is an intentional client-only singleton (and even then, prefer Pinia or createSharedComposable when SSR is not used).

Before you finish

  • Existing composables and @vueuse/core checked before creating a new one.
  • Inputs accept MaybeRefOrGetter<T> normalized via toValue() where reactivity is useful.
  • Returns a plain object of refs (with readonly() on internal state) and explicit action functions.
  • Listeners, timers and in-flight requests are cleaned up via onScopeDispose / onUnmounted / onWatcherCleanup.

Included files 2

  • LICENSE.txt
  • SKILL.md