---
trigger: glob
glob: src/*.vue
---


| No  | Category      | Guideline                            | Description                                                      | Do                                                | Don't                                        | Code Good                                          | Code Bad                                          | Severity | Docs URL                                                                                                 |
| --- | ------------- | ------------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------- | -------------------------------------------- | -------------------------------------------------- | ------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| 1   | Composition   | Use Composition API for new projects | Composition API offers better TypeScript support and logic reuse | `<script setup>` for components                   | Options API for new projects                 | `<script setup>`                                   | `export default { data() }`                       | Medium   | [Composition API FAQ](https://vuejs.org/guide/extras/composition-api-faq.html)                           |
| 2   | Composition   | Use script setup syntax              | Cleaner syntax with automatic exports                            | `<script setup>` with `defineProps`               | `setup()` function manually                  | `<script setup>`                                   | `<script> setup() { return {} }`                  | Low      | [SFC Script Setup](https://vuejs.org/api/sfc-script-setup.html)                                          |
| 3   | Reactivity    | Use ref for primitives               | ref() for primitive values that need reactivity                  | `ref()` for strings numbers booleans              | `reactive()` for primitives                  | `const count = ref(0)`                             | `const count = reactive(0)`                       | Medium   | [Reactivity Fundamentals](https://vuejs.org/guide/essentials/reactivity-fundamentals.html)               |
| 4   | Reactivity    | Use reactive for objects             | reactive() for complex objects and arrays                        | `reactive()` for objects with multiple properties | `ref()` for complex objects                  | `const state = reactive({ user: null })`           | `const state = ref({ user: null })`               | Medium   | -                                                                                                        |
| 5   | Reactivity    | Access ref values with .value        | Remember .value in script unwrap in template                     | Use `.value` in script                            | Forget .value in script                      | `count.value++`                                    | `count++ (in script)`                             | High     | -                                                                                                        |
| 6   | Reactivity    | Use computed for derived state       | Computed properties cache and update automatically               | `computed()` for derived values                   | Methods for derived values                   | `const doubled = computed(() => count.value * 2)`  | `const doubled = () => count.value * 2`           | Medium   | [Computed Properties](https://vuejs.org/guide/essentials/computed.html)                                  |
| 7   | Reactivity    | Use shallowRef for large objects     | Avoid deep reactivity for performance                            | `shallowRef` for large data structures            | `ref` for large nested objects               | `const bigData = shallowRef(largeObject)`          | `const bigData = ref(largeObject)`                | Medium   | [shallowRef](https://vuejs.org/api/reactivity-advanced.html#shallowref)                                  |
| 8   | Watchers      | Use watchEffect for simple cases     | Auto-tracks dependencies                                         | `watchEffect` for simple reactive effects         | `watch` with explicit deps when not needed   | `watchEffect(() => console.log(count.value))`      | `watch(count, (val) => console.log(val))`         | Low      | [Watchers](https://vuejs.org/guide/essentials/watchers.html)                                             |
| 9   | Watchers      | Use watch for specific sources       | Explicit control over what to watch                              | `watch` with specific refs                        | `watchEffect` for complex conditional logic  | `watch(userId, fetchUser)`                         | `watchEffect with conditionals`                   | Medium   | -                                                                                                        |
| 10  | Watchers      | Clean up side effects                | Return cleanup function in watchers                              | Return cleanup in `watchEffect`                   | Leave subscriptions open                     | `watchEffect((onCleanup) => { onCleanup(unsub) })` | `watchEffect without cleanup`                     | High     | -                                                                                                        |
| 11  | Props         | Define props with defineProps        | Type-safe prop definitions                                       | `defineProps` with TypeScript                     | Props without types                          | `defineProps<{ msg: string }>()`                   | `defineProps(['msg'])`                            | Medium   | [Typing Component Props](https://vuejs.org/guide/typescript/composition-api.html#typing-component-props) |
| 12  | Props         | Use withDefaults for default values  | Provide defaults for optional props                              | `withDefaults` with `defineProps`                 | Defaults in destructuring                    | `withDefaults(defineProps<Props>(), { count: 0 })` | `const { count = 0 } = defineProps()`             | Medium   | -                                                                                                        |
| 13  | Props         | Avoid mutating props                 | Props should be read-only                                        | Emit events to parent for changes                 | Direct prop mutation                         | `emit('update:modelValue', newVal)`                | `props.modelValue = newVal`                       | High     | -                                                                                                        |
| 14  | Emits         | Define emits with defineEmits        | Type-safe event emissions                                        | `defineEmits` with types                          | Emit without definition                      | `defineEmits<{ change: [id: number] }>()`          | `emit('change', id) without define`               | Medium   | [Typing Component Emits](https://vuejs.org/guide/typescript/composition-api.html#typing-component-emits) |
| 15  | Emits         | Use v-model for two-way binding      | Simplified parent-child data flow                                | v-model with modelValue prop                      | :value + @input manually                     | `<Child v-model="value"/>`                         | `<Child :value="value" @input="value = $event"/>` | Low      | [v-model](https://vuejs.org/guide/components/v-model.html)                                               |
| 16  | Lifecycle     | Use onMounted for DOM access         | DOM is ready in onMounted                                        | onMounted for DOM operations                      | Access DOM in setup directly                 | `onMounted(() => el.value.focus())`                | `el.value.focus() in setup`                       | High     | [Lifecycle Hooks](https://vuejs.org/api/composition-api-lifecycle.html)                                  |
| 17  | Lifecycle     | Clean up in onUnmounted              | Remove listeners and subscriptions                               | onUnmounted for cleanup                           | Leave listeners attached                     | `onUnmounted(() => window.removeEventListener())`  | No cleanup on unmount                             | High     | -                                                                                                        |
| 18  | Lifecycle     | Avoid onBeforeMount for data         | Use onMounted or setup for data fetching                         | Fetch in onMounted or setup                       | Fetch in onBeforeMount                       | `onMounted(async () => await fetchData())`         | `onBeforeMount(async () => await fetchData())`    | Low      | -                                                                                                        |
| 19  | Components    | Use single-file components           | Keep template script style together                              | .vue files for components                         | Separate template/script files               | Component.vue with all parts                       | Component.js + Component.html                     | Low      | -                                                                                                        |
| 20  | Components    | Use PascalCase for components        | Consistent component naming                                      | PascalCase in imports and templates               | kebab-case in script                         | `<MyComponent/>`                                   | `<my-component/>`                                 | Low      | [Component Naming](https://vuejs.org/style-guide/rules-strongly-recommended.html)                        |
| 21  | Components    | Prefer composition over mixins       | Composables replace mixins                                       | Composables for shared logic                      | Mixins for code reuse                        | `const { data } = useApi()`                        | `mixins: [apiMixin]`                              | Medium   | -                                                                                                        |
| 22  | Composables   | Name composables with use prefix     | Convention for composable functions                              | useFetch useAuth useForm                          | getData or fetchApi                          | `export function useFetch()`                       | `export function fetchData()`                     | Medium   | [Composables](https://vuejs.org/guide/reusability/composables.html)                                      |
| 23  | Composables   | Return refs from composables         | Maintain reactivity when destructuring                           | Return ref values                                 | Return reactive objects that lose reactivity | `return { data: ref(null) }`                       | `return reactive({ data: null })`                 | Medium   | -                                                                                                        |
| 24  | Composables   | Accept ref or value params           | Use toValue for flexible inputs                                  | toValue() or unref() for params                   | Only accept ref or only value                | `const val = toValue(maybeRef)`                    | `const val = maybeRef.value`                      | Low      | [toValue](https://vuejs.org/api/reactivity-utilities.html#tovalue)                                       |
| 25  | Templates     | Use v-bind shorthand                 | Cleaner template syntax                                          | :prop instead of v-bind:prop                      | Full v-bind syntax                           | `<div :class="cls">`                               | `<div v-bind:class="cls">`                        | Low      | -                                                                                                        |
| 26  | Templates     | Use v-on shorthand                   | Cleaner event binding                                            | @event instead of v-on:event                      | Full v-on syntax                             | `<button @click="handler">`                        | `<button v-on:click="handler">`                   | Low      | -                                                                                                        |
| 27  | Templates     | Avoid v-if with v-for                | v-if has higher priority causes issues                           | Wrap in template or computed filter               | v-if on same element as v-for                | `<template v-for><div v-if>`                       | `<div v-for v-if>`                                | High     | [Avoid v-if with v-for](https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for)        |
| 28  | Templates     | Use key with v-for                   | Proper list rendering and updates                                | Unique key for each item                          | Index as key for dynamic lists               | `v-for="item in items" :key="item.id"`             | `v-for="(item, i) in items" :key="i"`             | High     | -                                                                                                        |
| 29  | State         | Use Pinia for global state           | Official state management for Vue 3                              | Pinia stores for shared state                     | Vuex for new projects                        | `const store = useCounterStore()`                  | Vuex with mutations                               | Medium   | [Pinia](https://pinia.vuejs.org/)                                                                        |
| 30  | State         | Define stores with defineStore       | Composition API style stores                                     | Setup stores with defineStore                     | Options stores for complex state             | `defineStore('counter', () => {})`                 | `defineStore('counter', { state })`               | Low      | -                                                                                                        |
| 31  | State         | Use storeToRefs for destructuring    | Maintain reactivity when destructuring                           | storeToRefs(store)                                | Direct destructuring                         | `const { count } = storeToRefs(store)`             | `const { count } = store`                         | High     | [Destructuring from a Store](https://pinia.vuejs.org/core-concepts/#destructuring-from-a-store)          |
| 32  | Routing       | Use useRouter and useRoute           | Composition API router access                                    | useRouter() useRoute() in setup                   | this.$router this.$route                     | `const router = useRouter()`                       | `this.$router.push()`                             | Medium   | [Composition API Router](https://router.vuejs.org/guide/advanced/composition-api.html)                   |
| 33  | Routing       | Lazy load route components           | Code splitting for routes                                        | () => import() for components                     | Static imports for all routes                | `component: () => import('./Page.vue')`            | `component: Page`                                 | Medium   | [Lazy Loading](https://router.vuejs.org/guide/advanced/lazy-loading.html)                                |
| 34  | Routing       | Use navigation guards                | Protect routes and handle redirects                              | beforeEach for auth checks                        | Check auth in each component                 | `router.beforeEach((to) => {})`                    | Check auth in onMounted                           | Medium   | -                                                                                                        |
| 35  | Performance   | Use v-once for static content        | Skip re-renders for static elements                              | v-once on never-changing content                  | v-once on dynamic content                    | `<div v-once>{{ staticText }}</div>`               | `<div v-once>{{ dynamicText }}</div>`             | Low      | [v-once](https://vuejs.org/api/built-in-directives.html#v-once)                                          |
| 36  | Performance   | Use v-memo for expensive lists       | Memoize list items                                               | v-memo with dependency array                      | Re-render entire list always                 | `<div v-for v-memo="[item.id]">`                   | `<div v-for>` without memo                        | Medium   | [v-memo](https://vuejs.org/api/built-in-directives.html#v-memo)                                          |
| 37  | Performance   | Use shallowReactive for flat objects | Avoid deep reactivity overhead                                   | shallowReactive for flat state                    | reactive for simple objects                  | `shallowReactive({ count: 0 })`                    | `reactive({ count: 0 })`                          | Low      | -                                                                                                        |
| 38  | Performance   | Use defineAsyncComponent             | Lazy load heavy components                                       | defineAsyncComponent for modals dialogs           | Import all components eagerly                | `defineAsyncComponent(() => import())`             | `import HeavyComponent from`                      | Medium   | [defineAsyncComponent](https://vuejs.org/guide/components/async.html)                                    |
| 39  | TypeScript    | Use generic components               | Type-safe reusable components                                    | Generic with defineComponent                      | Any types in components                      | `<script setup lang="ts" generic="T">`             | `<script setup>` without types                    | Medium   | [TypeScript Composition API](https://vuejs.org/guide/typescript/composition-api.html)                    |
| 40  | TypeScript    | Type template refs                   | Proper typing for DOM refs                                       | ref<HTMLInputElement>(null)                       | ref(null) without type                       | `const input = ref<HTMLInputElement>(null)`        | `const input = ref(null)`                         | Medium   | -                                                                                                        |
| 41  | TypeScript    | Use PropType for complex props       | Type complex prop types                                          | PropType<User> for object props                   | Object without type                          | `type: Object as PropType<User>`                   | `type: Object`                                    | Medium   | -                                                                                                        |
| 42  | Testing       | Use Vue Test Utils                   | Official testing library                                         | mount shallowMount for components                 | Manual DOM testing                           | `import { mount } from '@vue/test-utils'`          | `document.createElement`                          | Medium   | [Vue Test Utils](https://test-utils.vuejs.org/)                                                          |
| 43  | Testing       | Test component behavior              | Focus on inputs and outputs                                      | Test props emit and rendered output               | Test internal implementation                 | `expect(wrapper.text()).toContain()`               | `expect(wrapper.vm.internalState)`                | Medium   | -                                                                                                        |
| 44  | Forms         | Use v-model modifiers                | Built-in input handling                                          | .lazy .number .trim modifiers                     | Manual input parsing                         | `<input v-model.number="age">`                     | `<input v-model="age"> then parse`                | Low      | [v-model Modifiers](https://vuejs.org/guide/essentials/forms.html#modifiers)                             |
| 45  | Forms         | Use VeeValidate or FormKit           | Form validation libraries                                        | VeeValidate for complex forms                     | Manual validation logic                      | `useField useForm from vee-validate`               | Custom validation in each input                   | Medium   | -                                                                                                        |
| 46  | Accessibility | Use semantic elements                | Proper HTML elements in templates                                | button nav main for purpose                       | div for everything                           | `<button @click>`                                  | `<div @click>`                                    | High     | -                                                                                                        |
| 47  | Accessibility | Bind aria attributes dynamically     | Keep ARIA in sync with state                                     | ":aria-expanded="isOpen""                         | Static ARIA values                           | ":aria-expanded="menuOpen""                        | `aria-expanded="true""`                           | Medium   | -                                                                                                        |
| 48  | SSR           | Use Nuxt for SSR                     | Full-featured SSR framework                                      | Nuxt 3 for SSR apps                               | Manual SSR setup                             | `npx nuxi init my-app`                             | Custom SSR configuration                          | Medium   | [Nuxt](https://nuxt.com/)                                                                                |
| 49  | SSR           | Handle hydration mismatches          | Client/server content must match                                 | ClientOnly for browser-only content               | Different content server/client              | `<ClientOnly><BrowserWidget/></ClientOnly>`        | `<div>{{ Date.now() }}</div>`                     | High     | -                                                                                                        |
