Vue 3 Composition API Conventions
Vue 3 rules for script-setup components, ref/computed discipline, composable design, and setup-style Pinia stores.
Use when: Use in Vue 3 + TypeScript projects to standardize script setup, composables, and Pinia patterns and to keep Options API code out.
The skill file
Cursor · .cursorrules.cursorrules3269 chars
You are working in a Vue 3 + TypeScript project. Composition API only — never generate Options API (`data()`, `methods`, `mounted()`); if you encounter it, flag it for migration rather than extending it.
## Single-file components
- Always `<script setup lang="ts">`, and block order is `<script setup>` → `<template>` → `<style scoped>`.
- Inside script setup, keep this order: imports, props/emits, composables/stores, local state, computed, watchers, functions, lifecycle hooks.
- Type-based declarations: `defineProps<{ item: Item; dense?: boolean }>()` with `withDefaults` for defaults, and `defineEmits<{ select: [id: string] }>()`. Never the runtime-object syntax.
- Use `defineModel<string>()` for v-model props — do not hand-wire `modelValue` prop + `update:modelValue` emit.
- Component files are PascalCase and multi-word (`UserCard.vue`, never `Card.vue`); reference them in templates as `<UserCard />`.
## Reactivity
- `ref()` for all local state, including objects — `reactive()` is banned in new code because destructuring silently kills its reactivity.
- Derived values are `computed()`, always. A `watch` that only assigns to another ref is a defect: replace with computed.
- `watch` with explicit sources over `watchEffect`; reserve `watchEffect` for fire-and-forget sync with obvious deps. Side-effect watchers that touch timers or listeners must register cleanup via `onWatcherCleanup`/returned stop handles.
- Never destructure props or a store directly. Use `toRefs(props)` / `storeToRefs(store)` when you need standalone refs.
- Templates auto-unwrap refs; in script, never forget `.value` — and never name a ref `value`.
## Composables
- Composables live in `src/composables/useX.ts`, one per file, named `useThing`. They take refs-or-raw via `toValue()` (`MaybeRefOrGetter<T>` params) so callers can pass either.
- A composable returns a plain object of refs and functions (`return { items, isLoading, refresh }`) — never a reactive object, so callers can destructure.
- Composables own their lifecycle: anything they set up (listeners, intervals, observers) is torn down in `onScopeDispose`. A composable the caller must remember to clean up is broken.
- Extract a composable when stateful logic is shared by 2+ components or wraps one external system (`useEventListener`, `useClipboard`). Do not extract single-use logic just to slim a component.
## Pinia
- Setup-style stores only: `export const useCartStore = defineStore("cart", () => { ... })` with refs as state, computeds as getters, functions as actions. No options-object stores.
- Store files: `src/stores/<name>.ts`, store id matches the filename.
- Cross-component shared state goes in a store; parent-child state stays in props/emits. Do not create a store to avoid passing two props.
- Async actions set their own `isLoading`/`error` refs inside the store; components never duplicate that bookkeeping.
- Mutate state only inside store actions. Components read via `storeToRefs` and call actions — no `store.count++` from a component.
## Templates
- `v-for` always has `:key` bound to a stable id, never the index. Never put `v-if` and `v-for` on the same element — filter in a computed.
- Multi-value class bindings use the object/array syntax, not string concatenation.
Install
drop into your repoSave this to your project or home directory so Cursor can load it.
path./.cursorrules
Discussion
What people are saying
Forks
0 forks of this skillLoading forks…