Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3.5+ Composition API reference with progressive sub-file loading for components, composables, reactivity, and testing.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/components.md
1# Vue Components23Patterns for Vue 3 components using Composition API with `<script setup>`.45## Quick Reference67| Pattern | Syntax |8| --------------------- | --------------------------------------------------------------- |9| Props (destructured) | `const { name = 'default' } = defineProps<{ name?: string }>()` |10| Props (template-only) | `defineProps<{ name: string }>()` |11| Emits | `const emit = defineEmits<{ click: [id: number] }>()` |12| Two-way binding | `const model = defineModel<string>()` |13| Slots shorthand | `<template #header>` not `<template v-slot:header>` |1415## Naming1617**Files:** PascalCase (`UserProfile.vue`) OR kebab-case (`user-profile.vue`) - be consistent1819**Component names in code:** Always PascalCase2021**Composition:** General → Specific: `SearchButtonClear.vue` not `ClearSearchButton.vue`2223## Props2425**Destructure with defaults (Vue 3.5+)** when used in script or need defaults:2627```ts28const { count = 0, message = 'Hello' } = defineProps<{29count?: number30message?: string31required: boolean32}>()3334// Use directly - maintains reactivity35console.log(count + 1)3637// ⚠️ When passing to watchers/functions, wrap in getter:38watch(() => count, (newVal) => { ... }) // ✅ Correct39watch(count, (newVal) => { ... }) // ❌ Won't work40```4142**Non-destructured** only if props ONLY used in template:4344```ts45defineProps<{ count: number }>()46// Template: {{ count }}47```4849**Same-name shorthand (Vue 3.4+):** `:count` instead of `:count="count"`5051```vue52<MyComponent :count :user :items />53<!-- Same as: :count="count" :user="user" :items="items" -->54```5556[Reactive destructuring docs](https://vuejs.org/guide/components/props#reactive-props-destructure)5758## Emits5960Type-safe event definitions:6162```ts63const emit = defineEmits<{64update: [id: number, value: string] // multiple args65close: [] // no args66}>()6768// Usage69emit('update', 123, 'new value')70emit('close')71```7273**Template syntax:** kebab-case (`@update-item`) vs camelCase in script (`updateItem`)7475## Slots7677**Always use shorthand:** `<template #header>` not `<template v-slot:header>`7879**Always explicit `<template>` tags** for all slots8081```vue82<template>83<Card>84<template #header>85<h2>Title</h2>86</template>87<template #default>88Content89</template>90</Card>91</template>92```9394## defineModel() - Two-Way Binding9596Replaces manual `modelValue` prop + `update:modelValue` emit.9798### Basic99100```vue101<script setup lang="ts">102const title = defineModel<string>()103</script>104105<template>106<input v-model="title">107</template>108```109110### With Options111112```vue113<script setup lang="ts">114const [title, modifiers] = defineModel<string>({115default: 'default value',116required: true,117get: (value) => value.trim(),118set: (value) => {119if (modifiers.capitalize) {120return value.charAt(0).toUpperCase() + value.slice(1)121}122return value123},124})125</script>126```127128**⚠️ Warning:** When using `default` without parent providing a value, parent and child can de-sync (parent `undefined`, child has default). Always provide matching defaults in parent or make prop required.129130**Prevent double-emit with `required: true`:**131132```ts133// ❌ Without required - emits twice (undefined then value)134const model = defineModel<Item>()135136// ✅ With required - single emit137const model = defineModel<Item>({ required: true })138```139140Use `required: true` when the model should always have a value to avoid the double-emit issue during initialization.141142### Multiple Models143144Default assumes `modelValue` prop. For multiple bindings, use explicit names:145146```vue147<script setup lang="ts">148const firstName = defineModel<string>('firstName')149const age = defineModel<number>('age')150</script>151152<!-- Usage -->153<UserForm v-model:first-name="user.firstName" v-model:age="user.age" />154```155156[v-model modifiers docs](https://vuejs.org/guide/components/v-model#handling-v-model-modifiers)157158## Reusable Templates159160For typed, scoped template snippets within a component:161162```vue163<script setup lang="ts">164import { createReusableTemplate } from '@vueuse/core'165166const [DefineItem, UseItem] = createReusableTemplate<{167item: SearchItem168icon: string169color?: 'red' | 'green' | 'blue'170}>()171</script>172173<template>174<DefineItem v-slot="{ item, icon, color }">175<div :class="color">176<Icon :name="icon" />177{{ item.name }}178</div>179</DefineItem>180181<!-- Reuse multiple times -->182<UseItem v-for="item in items" :key="item.id" :item :icon="getIcon(item)" />183</template>184```185186## Template Refs (Vue 3.5+)187188Use `useTemplateRef()` for type-safe template references with IDE support:189190```vue191<script setup lang="ts">192import { useTemplateRef, onMounted } from 'vue'193194const input = useTemplateRef<HTMLInputElement>('my-input')195196onMounted(() => {197input.value?.focus()198})199</script>200201<template>202<input ref="my-input">203</template>204```205206**Benefits over `ref()`:**207208- Type-safe with generics209- Better IDE autocomplete and refactoring210- Explicit ref name as string literal211212**Dynamic refs:**213214```vue215<script setup lang="ts">216const items = ref(['a', 'b', 'c'])217const itemRefs = useTemplateRef<HTMLElement>('item')218219// Access refs after mount220onMounted(() => {221console.log(itemRefs.value) // Array of elements222})223</script>224225<template>226<div v-for="item in items" :key="item" ref="item">227{{ item }}228</div>229</template>230```231232**Component refs with generics:**233234For generic components, use `ComponentExposed` from `vue-component-type-helpers`:235236```ts237import type { ComponentExposed } from 'vue-component-type-helpers'238import MyGenericComponent from './MyGenericComponent.vue'239240// Get exposed methods/properties with correct generic types241const compRef = useTemplateRef<ComponentExposed<typeof MyGenericComponent>>('comp')242243onMounted(() => {244compRef.value?.someExposedMethod() // Typed!245})246```247248Install: `pnpm add -D vue-component-type-helpers`249250## SSR Hydration (Vue 3.5+)251252**Suppress hydration mismatches** for values that differ between server/client:253254```vue255<template>256<!-- Client-side only values -->257<span data-allow-mismatch>{{ new Date().toLocaleString() }}</span>258259<!-- Specific mismatch types -->260<span data-allow-mismatch="text">{{ timestamp }}</span>261<span data-allow-mismatch="children">262<ClientOnly>...</ClientOnly>263</span>264<span data-allow-mismatch="style">...</span>265<span data-allow-mismatch="class">...</span>266<span data-allow-mismatch="attribute">...</span>267</template>268```269270**Generate SSR-stable IDs:**271272```vue273<script setup lang="ts">274import { useId } from 'vue'275276const id = useId() // Stable across server/client renders277</script>278279<template>280<label :for="id">Name</label>281<input :id="id">282</template>283```284285## Deferred Teleport (Vue 3.5+)286287Teleport to elements rendered later in the same cycle:288289```vue290<template>291<!-- This renders first -->292<Teleport defer to="#late-div">293<span>Deferred content</span>294</Teleport>295296<!-- This renders after, but Teleport waits -->297<div id="late-div"></div>298</template>299```300301Without `defer`, teleport to `#late-div` would fail since it doesn't exist yet.302303## Common Mistakes304305**Using `const props =` with destructured values:**306307```ts308// ❌ Wrong309const props = defineProps<{ count: number }>()310const { count } = props // Loses reactivity311```312313**Forgetting TypeScript types:**314315```ts316// ❌ Wrong317const emit = defineEmits(['update'])318319// ✅ Correct320const emit = defineEmits<{ update: [id: number] }>()321```322323**Components >300 lines:** Split into smaller components or extract logic to composables324