Use when working with Vue 3 components, Single-File Components, Composition API, reactivity, props/events, v-model, watchers, and TypeScript patterns based on official Vue documentation.
Use this skill when building or reviewing Vue 3 applications and components, especially when work involves Single-File Components, Composition API, template bindings, reactivity, and typed component APIs.
This skill is based on official Vue documentation:
This skill applies when tasks involve:
.vue) and <script setup>.ref() and reactive().v-model across inputs, textareas, selects, and custom components.<script setup> for build-tool-based applications.ref() as the primary API for reactive state, especially for primitives and state that may be replaced wholesale.reactive() for object-shaped state when stable object identity is appropriate.defineProps() and defineEmits().<script setup lang="ts"> using type-based declarations when practical.reactive()..value in JavaScript when using refs outside templates.obj.count directly instead of using a getter like () => obj.count.nextTick() when post-update state matters.v-model while expecting initial DOM attributes (value, checked, selected) to remain the source of truth.<script setup> with ref() for local state<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">Count is: {{ count }}</button>
</template>
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: ['Vue 2', 'Vue 3']
})
const hasPublishedBooks = computed(() => author.books.length > 0)
</script>
<template>
<span>{{ hasPublishedBooks ? 'Yes' : 'No' }}</span>
</template>
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('')
const loading = ref(false)
watch(question, async (newQuestion, _oldQuestion, onCleanup) => {
if (!newQuestion.includes('?')) {
return
}
const controller = new AbortController()
onCleanup(() => controller.abort())
loading.value = true
answer.value = 'Thinking...'
try {
const response = await fetch('https://yesno.wtf/api', {
signal: controller.signal,
})
answer.value = (await response.json()).answer
} finally {
loading.value = false
}
})
</script>
<script setup lang="ts">
const props = defineProps<{
title: string
disabled?: boolean
}>()
const emit = defineEmits<{
save: [id: number]
}>()
function save() {
emit('save', 1)
}
</script>
<template>
<button :disabled="props.disabled" @click="save">{{ props.title }}</button>
</template>
v-model as the JavaScript-state source of truth<script setup>
import { ref } from 'vue'
const message = ref('')
const selected = ref('')
</script>
<template>
<input v-model.trim="message" placeholder="Edit me" />
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
</select>
</template>
nextTick() when post-update DOM access is requiredimport { ref, nextTick } from 'vue'
const count = ref(0)
async function incrementAndMeasure() {
count.value++
await nextTick()
// DOM now reflects the latest count
}
Component structure:
<script setup> used where appropriate?Reactivity:
ref() or reactive() chosen for the right reason?.value in JavaScript and not misused after destructuring?Derived state and side effects:
computed()?deep, immediate, flush) when needed?Components and templates:
Forms:
v-model used with the correct element semantics?.trim, .number, and .lazy chosen intentionally?TypeScript:
withDefaults or reactive props destructure used correctly for prop defaults?When producing Vue guidance or code:
<script setup> unless the use case clearly favors another style.computed) from side effects (watch / watchEffect).