Vue's TypeScript story is compiler-driven: the SFC compiler reads your type annotations and generates runtime declarations from them. The chapters list exactly which annotation goes where, and the interview questions follow: "how do you type emits", "how do you type a template ref to a component", "why can't reactive take a generic".
create-vue scaffolds a typed project; vue-tsc type-checks SFCs on the command line (tsc cannot read .vue files); the Vue (Official) VS Code extension provides IDE support. Base configs come from @vue/tsconfig.isolatedModules: true (each file transpiled alone, as Vite/esbuild do), verbatimModuleSyntax: true (explicit import type), strict: true, moduleResolution: 'bundler'; include .vue files through env.d.ts shims when needed.defineComponent() enables type inference for Options API components and for setup() props; <script setup lang="ts"> needs no wrapper.(x as string).toUpperCase() when lang="ts" is set; the compiler strips it.<script setup lang="ts" generic="T extends string | number, U extends Item">; the generic names are usable in defineProps, defineEmits, defineSlots and can reference imported types.defineProps<{ foo: string, bar?: number }>(); the type argument must be a type literal or a reference to an interface/type alias in the same file, or (3.3+) an imported type with some limitations; defaults via withDefaults(defineProps<Props>(), { bar: 0 }) or reactive props destructure (3.5+); runtime-declared complex types use PropType<Book> (type: Object as PropType<Book>).defineEmits<{ (e: 'change', id: number): void }>() (call signature) or the 3.3+ named tuple syntax defineEmits<{ change: [id: number] }>().ref<T>(): ref<string | number>('2020'); ref<number>() without a value is Ref<number | undefined>. reactive is annotated with an interface, not a generic argument, because the returned type differs from the argument through nested ref unwrapping. computed<number>(() => …) when inference needs help.(event: Event)) and cast the target ((event.target as HTMLInputElement).value).const key = Symbol() as InjectionKey<string> types both sides; a string key needs inject<string>('foo') (returns string | undefined) or inject('foo') as string when you know it exists.useTemplateRef<HTMLInputElement>('el'); in 3.5 with the Vue extension, the type is inferred automatically from the matching ref attribute. Component refs: InstanceType<typeof MyModal>; for generic or dynamic components use ComponentExposed<typeof MyGenericModal> from vue-component-type-helpers.defineComponent. Props: type: Object as PropType<Book>, required: true for non-optional; arrow functions for validator/default lose this typing, so use method shorthand.declare module 'vue' { interface ComponentCustomProperties { $http: typeof axios } } in a .ts file that is a module (add export {}). Custom options (beforeRouteEnter) via ComponentCustomOptions.PropType<T> (runtime prop declarations), MaybeRef<T>, MaybeRefOrGetter<T> (composable inputs with toValue), ExtractPropTypes<T> (resolved props, defaults applied) and ExtractPublicPropTypes<T> (what the parent may pass, optional props stay optional), ComponentCustomProperties, ComponentCustomOptions, ComponentCustomProps, CSSProperties.<script setup lang="ts" generic="T extends { id: string }">
import type { InjectionKey } from 'vue'
import type { ComponentExposed } from 'vue-component-type-helpers'
import GenericModal from './GenericModal.vue'
// Props from a type literal, default via reactive destructure (3.5+)
const { items, dense = false } = defineProps<{ items: T[], dense?: boolean }>()
// Named tuple emits (3.3+)
const emit = defineEmits<{ select: [item: T], close: [] }>()
// Typed slots against the same generic
defineSlots<{ row(props: { item: T, index: number }): any }>()
// Refs: explicit generic; without initial value the type includes undefined
const selected = ref<T>()
const query = ref<string>('')
// reactive: annotate with an interface, not a generic argument
interface FormState { touched: boolean, errors: string[] }
const form: FormState = reactive({ touched: false, errors: [] })
// Provide/inject with a typed key
const CONFIG: InjectionKey<{ dense: boolean }> = Symbol('config')
provide(CONFIG, { dense })
// Template refs: element and generic component
const input = useTemplateRef<HTMLInputElement>('input')
const modal = useTemplateRef<ComponentExposed<typeof GenericModal>>('modal')
function onInput(event: Event) {
query.value = (event.target as HTMLInputElement).value
}
</script>
<template>
<input ref="input" :value="query" @input="onInput">
<ul>
<li v-for="(item, index) in items" :key="item.id" @click="selected = item; emit('select', item)">
<slot name="row" :item :index>{{ item.id }}</slot>
</li>
</ul>
<GenericModal ref="modal" @close="emit('close')" />
</template>
Options API augmentation, the part everyone has to look up:
import type { AxiosInstance } from 'axios'
declare module 'vue' {
interface ComponentCustomProperties {
$http: AxiosInstance
}
}
export {} // makes this file a module so the augmentation applies
reactive<{ count: Ref<number> }>({ count: ref(0) }) fails because the returned type unwraps nested refs (count becomes number). Annotate the variable with an interface describing the unwrapped shape and let reactive infer.
The current recommendation for typing template refs to generic components (ComponentExposed from vue-component-type-helpers) and whether the Vue extension now infers useTemplateRef types without the generic argument.
defineEmits from the call-signature syntax to named tuples and confirm both produce the same errors on a wrong payload.ComponentExposed and call an exposed method with the wrong argument type.$http global property with the augmentation above, then remove export {} and observe how the augmentation breaks."Props with a type argument to defineProps, defaults through withDefaults or reactive destructure, and PropType only when I declare props at runtime with complex types. Emits with the named tuple syntax, defineEmits<{ change: [id: number] }>(), which reads as a contract. Refs with an explicit generic when the initial value does not tell the whole story, remembering that ref<T>() without a value includes undefined, and reactive with an interface annotation on the variable because its return type unwraps nested refs. Template refs with useTemplateRef<HTMLInputElement>, and component refs with InstanceType<typeof Comp>, or ComponentExposed from the type helpers when the component is generic."
Nothing yet. Notes filed here show up on this page and the section overview.
Best practices — deployment, performance, accessibility, security
The four Best Practices chapters as checklists — production builds and error tracking, the guide's page-load and update-performance techniques, its accessibility guidance, and the security rules Vue enforces and the ones it cannot.
Extra topics — internals and the edges
Ways of Using Vue, the Composition API FAQ, Reactivity in Depth, the Rendering Mechanism, Render Functions and JSX, Vue and Web Components, and Animation Techniques, as the guide presents them.