Jesse A. Green / notebook

Vue User Guide

Generated June 2026 from vuejs/core at 86ad076

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

Vue (vue) is the progressive JavaScript framework for building modern web UI. This guide covers everything you need to use Vue in your applications: installing it, creating reactive state, building components, wiring up the template syntax, rendering on the server, and shipping framework-grade widgets as native custom elements.

This document is built directly from the source of the vuejs/core monorepo at version 3.5.35. Written from the package entry points and implementation. Where Vue exposes the same function from several packages, this guide tells you which import path to use.

Note

vuejs/core is the home of the core Vue runtime and compiler. It is published to npm as the vue package, plus a family of scoped packages (@vue/reactivity, @vue/runtime-core, @vue/runtime-dom, @vue/server-renderer, @vue/compiler-sfc, and more). When this guide says "import from vue", it refers to the umbrella package that re-exports the runtime + compiler for browser apps.


1. Quick Start

The fastest way to a running Vue app is the official scaffolding tool, which sets up Vite, single-file components, and sensible defaults.

npm create vue@latest
Vue.js - The Progressive JavaScript Framework

✔ Project name: … my-app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
...
Scaffolding project in ./my-app...
Done.

Then install and start the dev server:

cd my-app
npm install
npm run dev
  VITE v8.0.0  ready in 312 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose

If you just want to try Vue on a single HTML page with no build step, load the global build from a CDN and call createApp:

<div id="app">{{ message }}</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const message = ref('Hello Vue!')
      return { message }
    },
  }).mount('#app')
</script>

Tip

The CDN snippet above uses the full build (vue.global.js), which includes the template compiler so that the in-DOM {{ message }} template can be compiled in the browser. For production apps with a build step, you use the runtime-only build, where templates are compiled ahead of time. See Choosing a Build.


2. Installation

2.1 Install from npm

npm install vue
added 1 package in 1s

Vue ships as an ES module and a CommonJS module, with TypeScript declarations included. The published vue package depends on the internal Vue packages (@vue/runtime-dom, @vue/compiler-dom, @vue/compiler-sfc, @vue/server-renderer, @vue/shared), so a single install brings the whole framework.

Note

Vue's npm package marks typescript as an optional peer dependency. You do not need TypeScript to use Vue, but if you have it installed, Vue's single-file-component tooling can use it for type-aware compilation of <script setup lang="ts">.

2.2 Choosing a build

Vue publishes several build formats from the same source. The right one depends on whether you use a bundler and whether you compile templates ahead of time.

Build file Format Includes compiler? Use when
vue.runtime.esm-bundler.js ESM (bundler) No Default for Vite/webpack/Rollup apps with SFCs (the module entry)
vue.esm-bundler.js ESM (bundler) Yes Bundler apps that compile templates at runtime
vue.esm-browser.js ESM (browser) Yes Native <script type="module"> with no bundler
vue.runtime.esm-browser.js ESM (browser) No Native ESM, templates precompiled
vue.global.js IIFE (window.Vue) Yes Plain <script> tag, dev
vue.global.prod.js IIFE, minified Yes Plain <script> tag, production
vue.cjs.js / vue.cjs.prod.js CommonJS Yes Node.js require() (SSR, tests)

Important

The package's default import entry resolves to vue.runtime.esm-bundler.js — the runtime-only build. This is intentional: in a real app your .vue single-file components are compiled to render functions at build time, so the in-browser compiler is dead weight. If you pass a string template option at runtime with a runtime-only build, Vue cannot compile it and warns you (see Troubleshooting).

2.3 Bundler feature flags

When you use a bundler build, Vue exposes three global compile-time flags you should define for the smallest, most correct production bundle. Define them in your bundler config (Vite's @vitejs/plugin-vue sets sensible defaults automatically):

Flag Default Effect
__VUE_OPTIONS_API__ true Set to false to tree-shake the Options API if you use only Composition API
__VUE_PROD_DEVTOOLS__ false Set to true to enable devtools support in production builds
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ false Set to true to get detailed hydration-mismatch warnings in production
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  define: {
    __VUE_OPTIONS_API__: 'false',
    __VUE_PROD_DEVTOOLS__: 'false',
  },
})

Tip

Turning off __VUE_OPTIONS_API__ removes the Options-API compatibility layer from your bundle. Only do this if every component in your app (and your dependencies) uses the Composition API exclusively.

2.4 System requirements

Requirement Value
Node.js (for tooling/SSR) >= 20.0.0
Browser target (default builds) ES2016+ (modern evergreen browsers)
TypeScript (optional) ~5.6 recommended for the SFC type macros

3. Creating an Application

Every Vue app starts with createApp (or createSSRApp for server-rendered apps), imported from vue.

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)
app.mount('#app')

createApp returns an application instance. You configure the app on this instance before calling .mount().

3.1 The application instance API

Method Purpose
app.mount(rootContainer) Render the root component into a DOM element or CSS selector string. Returns the root component instance.
app.unmount() Tear down the mounted app.
app.component(name, component?) Register (or retrieve) a global component.
app.directive(name, directive?) Register (or retrieve) a global custom directive.
app.use(plugin, ...options) Install a plugin.
app.mixin(mixin) Apply a global mixin (Options API).
app.provide(key, value) Provide a value to all descendant components via injection.
app.runWithContext(fn) Run a callback with the app as the active injection context.
app.config The app-level configuration object (see below).
app.version The Vue version string this app was created with.
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import MyButton from './components/MyButton.vue'

const app = createApp(App)

app.use(router)              // install a plugin
app.component('MyButton', MyButton)  // global component
app.provide('apiBase', '/api/v1')    // app-level injection

app.mount('#app')

Important

app.mount() clears the existing content of the target container before rendering. Anything already inside #app is removed (except when you intentionally use in-DOM templates with the full build).

3.2 Application configuration (app.config)

Config key Purpose
errorHandler Global handler for uncaught errors from components, watchers, and lifecycle hooks.
warnHandler Custom handler for dev-mode warnings.
performance Set true to enable component init/render/patch performance markers in the browser timeline.
compilerOptions Options passed to the in-browser template compiler (full build only).
globalProperties An object whose properties are accessible on every component instance (e.g. app.config.globalProperties.$http).
optionMergeStrategies Custom merge strategies for custom component options.
app.config.errorHandler = (err, instance, info) => {
  // report to your error tracking service
  console.error('Vue error:', err, info)
}

app.config.globalProperties.$formatDate = (d) => new Date(d).toLocaleDateString()

Caution

app.config.isCustomElement is deprecated. To tell Vue which tags are custom elements, use app.config.compilerOptions.isCustomElement instead. With a runtime-only build, isCustomElement must be configured in your build tool (e.g. @vitejs/plugin-vue), because there is no runtime compiler to read the option.


4. Reactivity

Reactivity is the heart of Vue. The reactivity APIs are exported from both vue and the standalone @vue/reactivity package, so you can use them in plain JavaScript with no components at all.

4.1 Refs

A ref is a reactive container for a single value. Read and write it through its .value property.

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
0
1

In templates, refs are unwrapped automatically — you write {{ count }}, not {{ count.value }}.

Function Purpose
ref(value) Create a deeply-reactive ref. Objects assigned to a ref are made reactive.
shallowRef(value) Create a ref that only tracks .value reassignment, not deep mutations.
isRef(r) Return true if a value is a ref.
unref(r) Return r.value if r is a ref, otherwise r.
toRef(source, key?, default?) Create a ref that stays in sync with a property of a reactive object (or normalize a value/getter to a ref).
toRefs(object) Convert all properties of a reactive object into refs, preserving reactivity (great for destructuring).
toValue(source) Normalize a value, ref, or getter function to its plain value.
proxyRefs(object) Return a proxy that auto-unwraps any refs accessed on it (used internally for setup returns).
customRef(factory) Create a ref with custom dependency-tracking and trigger control (e.g. debounced refs).
triggerRef(ref) Force effects depending on a shallowRef to run, after a deep mutation.
import { ref, toRefs, reactive } from 'vue'

const state = reactive({ first: 'Ada', last: 'Lovelace' })

// Destructuring a reactive object loses reactivity — use toRefs:
const { first, last } = toRefs(state)
first.value = 'Grace' // still updates state.first

Tip

Use customRef for patterns like a debounced search box. The factory receives track and trigger callbacks so you control exactly when reads are recorded and when updates fire:

import { customRef } from 'vue'
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => ({
    get() { track(); return value },
    set(newValue) {
      clearTimeout(timeout)
      timeout = setTimeout(() => { value = newValue; trigger() }, delay)
    },
  }))
}

4.2 Reactive objects

reactive makes an entire object (or array, Map, Set) deeply reactive using a JavaScript Proxy.

import { reactive } from 'vue'

const book = reactive({ title: 'Vue Internals', pages: 320 })
book.pages = 321 // triggers any effect that read book.pages
Function Purpose
reactive(target) Deeply reactive proxy of an object.
shallowReactive(target) Only the root-level properties are reactive.
readonly(target) A deeply read-only proxy; writes are blocked (and warn in dev).
shallowReadonly(target) Only root-level properties are read-only.
isReactive(value) true for reactive/shallowReactive proxies.
isReadonly(value) true for readonly/shallowReadonly proxies.
isProxy(value) true if the value is any reactive or readonly proxy.
isShallow(value) true for shallow reactive/readonly proxies.
toRaw(proxy) Return the original, non-reactive object behind a proxy.
markRaw(object) Mark an object so it is never converted to a proxy.
import { reactive, readonly, toRaw, markRaw } from 'vue'

const original = { count: 0 }
const copy = reactive(original)
console.log(toRaw(copy) === original) // true

const config = markRaw({ thirdPartyInstance: new SomeBigClass() })
const state = reactive({ config }) // state.config stays raw

Warning

reactive only works on objects (including arrays and collection types). It cannot make a primitive value (string, number, boolean) reactive — use ref for those. Also note: destructuring a reactive object disconnects the resulting variables from reactivity; use toRefs to keep the link. Finally, replacing a reactive variable wholesale (state = reactive({...})) breaks reactivity because you discard the original proxy.

Note

reactive silently skips objects that are not extensible (frozen or sealed) — calling reactive on a frozen object returns it unchanged rather than wrapping it. This is reported behavior (issue #14893).

4.3 Computed properties

computed creates a reactive ref whose value is derived from other reactive sources and cached until a dependency changes.

import { ref, computed } from 'vue'

const count = ref(1)
const double = computed(() => count.value * 2)

console.log(double.value) // 2
count.value = 5
console.log(double.value) // 10
2
10

Pass an object with get and set to create a writable computed:

import { ref, computed } from 'vue'

const first = ref('Ada')
const last = ref('Lovelace')

const fullName = computed({
  get: () => `${first.value} ${last.value}`,
  set: (val) => {
    [first.value, last.value] = val.split(' ')
  },
})

fullName.value = 'Grace Hopper' // updates first and last

Tip

A computed is cached — its getter only re-runs when a reactive dependency it read actually changes. This makes computed the right tool for expensive derivations. For side effects (logging, fetching), use a watcher instead. In development, pass debug options to inspect dependencies:

const double = computed(() => count.value * 2, {
  onTrack(e) { /* called when a dep is tracked */ },
  onTrigger(e) { /* called when a dep triggers */ },
})

4.4 Watchers

Watchers run a callback in response to reactive changes. Vue offers watch (explicit sources) and watchEffect (automatic dependency tracking).

Function Behavior
watch(source, cb, options?) Run cb when source changes. Lazy by default.
watchEffect(effect, options?) Run effect immediately, re-running whenever any reactive value it reads changes.
watchPostEffect(effect) watchEffect with flush: 'post' (runs after DOM updates).
watchSyncEffect(effect) watchEffect with flush: 'sync' (runs synchronously on each change).
import { ref, watch, watchEffect } from 'vue'

const query = ref('')
const results = ref([])

// Explicit source, gets old and new values:
watch(query, async (newQuery, oldQuery) => {
  results.value = await search(newQuery)
})

// Automatic tracking — re-runs whenever query.value changes:
watchEffect(() => {
  console.log(`Searching for: ${query.value}`)
})

watch options:

Option Type Purpose
immediate boolean Run the callback once immediately on creation.
deep boolean | number Deeply traverse objects so nested mutations trigger the callback. A number limits traversal depth.
flush 'pre' | 'post' | 'sync' When to run relative to component updates (default 'pre').
once boolean Run the callback at most once, then stop.
onTrack / onTrigger function Dev-only debugging hooks.

You can watch multiple sources by passing an array, and use getters for reactive-object properties:

import { reactive, watch } from 'vue'

const state = reactive({ a: 1, b: 2 })

watch(
  () => [state.a, state.b],
  ([a, b], [prevA, prevB]) => {
    console.log(`a: ${prevA} -> ${a}, b: ${prevB} -> ${b}`)
  },
)

Cleaning up side effects. Inside a watcher you can register cleanup work that runs before the next invocation (or when the watcher stops) with onWatcherCleanup:

import { ref, watch, onWatcherCleanup } from 'vue'

const id = ref(1)
watch(id, (newId) => {
  const controller = new AbortController()
  fetch(`/api/items/${newId}`, { signal: controller.signal })
  onWatcherCleanup(() => controller.abort()) // cancel stale request
})

Stopping a watcher. watch and watchEffect return a stop handle. Calling it stops the watcher; it also exposes .pause() and .resume():

const stop = watchEffect(() => { /* ... */ })
stop.pause()   // temporarily stop reacting
stop.resume()  // resume
stop()         // stop permanently

Warning

When you set immediate: true together with an empty/never-changing array of sources, the callback runs once on creation but never again — there are no sources to react to. This is expected behavior and a common point of confusion (issue #14898). If you want a callback that re-runs on any dependency, use watchEffect.

Important

By default watch is shallow for reactive getters that return the object itself — but when you pass a reactive object directly as the source, Vue forces deep: true. To watch a specific nested value efficiently, pass a getter (() => state.some.nested) instead of the whole object.

4.5 Effect scopes

When you create watchers or computed values outside of a component (for example in a composable used at module scope), you can group their reactive effects into a scope so they can be disposed together.

import { effectScope, ref, watchEffect } from 'vue'

const scope = effectScope()

scope.run(() => {
  const count = ref(0)
  watchEffect(() => console.log(count.value))
})

// Later — dispose every effect created inside the scope at once:
scope.stop()
API Purpose
effectScope(detached?) Create a scope. A detached scope is not collected by an outer scope.
scope.run(fn) Run a function with this scope active; effects created inside are captured.
scope.stop() Stop all effects captured by the scope.
getCurrentScope() Return the currently active effect scope, if any.
onScopeDispose(fn) Register a callback to run when the current scope is stopped.

Tip

onScopeDispose is the composition-friendly way to clean up in a composable without relying on a component lifecycle hook — it works anywhere there is an active scope (including inside a component's setup).

4.6 Low-level effects

For advanced use, effect and ReactiveEffect let you run a function that re-executes whenever its reactive dependencies change — this is the primitive that watchers and the renderer are built on.

import { reactive, effect, stop } from 'vue'

const state = reactive({ n: 0 })
const runner = effect(() => console.log(state.n))
// logs 0 immediately

state.n = 1 // logs 1

stop(runner) // stop re-running

Caution

effect is a low-level API. In application code prefer watchEffect, which integrates with the component lifecycle and is automatically stopped when the component unmounts. Reach for effect/ReactiveEffect/stop only when building reactivity infrastructure.


5. Components

5.1 Single-File Components (SFCs)

The idiomatic way to author Vue components is the .vue single-file component, which co-locates template, logic, and scoped styles:

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">Count is: {{ count }}</button>
</template>

<style scoped>
button { font-weight: bold; }
</style>

<script setup> is compile-time sugar: every top-level binding (imports, variables, functions) is automatically exposed to the template. This is processed by @vue/compiler-sfc at build time.

5.2 The setup function and the Composition API

Without SFC sugar, a component defines a setup() function that returns the state and methods used by the template:

import { defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const count = ref(0)
    const increment = () => count.value++
    return { count, increment }
  },
})

defineComponent is a no-runtime-cost helper that gives you full TypeScript inference for component options. Use it whenever you define a component as a plain object (especially in TypeScript).

5.3 <script setup> compiler macros

Inside <script setup>, these compiler macros declare a component's interface. They are not imported at runtime — the SFC compiler transforms them — but Vue exports runtime stubs for typing and dev warnings.

Macro Purpose
defineProps Declare the component's props.
defineEmits Declare the events the component emits.
defineExpose Explicitly expose properties to a parent via template refs.
defineOptions Declare component options (e.g. name, inheritAttrs) inline.
defineSlots Declare the slots the component accepts (typing only).
defineModel Declare a two-way bindable model backed by a prop + update: event.
withDefaults Provide default values for type-only defineProps.
<script setup>
const props = defineProps({
  title: { type: String, required: true },
  count: { type: Number, default: 0 },
})

const emit = defineEmits(['submit', 'cancel'])

function save() {
  emit('submit', props.title)
}
</script>

Two-way binding with defineModel:

<!-- CustomInput.vue -->
<script setup>
const model = defineModel()  // backed by the `modelValue` prop + `update:modelValue`
</script>

<template>
  <input v-model="model" />
</template>
<!-- Parent -->
<CustomInput v-model="searchText" />

5.4 Composition helpers

These functions are callable inside setup (or <script setup>) to access the component's runtime context:

Function Returns / Purpose
useAttrs() The fallthrough attributes (everything not declared as a prop or emit).
useSlots() The slots passed to this component.
useModel(props, name, options?) The runtime helper behind defineModel.
useTemplateRef(key) A ref bound to a template element/component with a matching ref="key".
useId() A stable, SSR-safe unique ID (for for/aria-* attribute pairing).
getCurrentInstance() The internal component instance (advanced/plugin use).
hasInjectionContext() true if called where inject() would work.
<script setup>
import { useTemplateRef, onMounted, useId } from 'vue'

const inputRef = useTemplateRef('the-input')
const id = useId()

onMounted(() => inputRef.value.focus())
</script>

<template>
  <label :for="id">Name</label>
  <input :id="id" ref="the-input" />
</template>

Tip

useId generates IDs that match between the server-rendered HTML and the client, so it is the correct way to create unique IDs in apps that use SSR — hand-rolled counters cause hydration mismatches.

5.5 Lifecycle hooks

Register lifecycle callbacks inside setup. Each hook runs at a defined point in a component's life.

Hook Fires
onBeforeMount Before the component's first render/DOM insertion.
onMounted After the component is inserted into the DOM.
onBeforeUpdate Before a re-render caused by reactive change.
onUpdated After the DOM has been patched.
onBeforeUnmount Before the component is removed.
onUnmounted After the component and its effects are torn down.
onActivated When a <KeepAlive>-cached component is re-inserted.
onDeactivated When a <KeepAlive>-cached component is removed but kept alive.
onErrorCaptured When an error from a descendant is captured (return false to stop propagation).
onRenderTracked (dev) When a reactive dependency is tracked during render.
onRenderTriggered (dev) When a dependency triggers a re-render.
onServerPrefetch (SSR) Await async data before the component renders on the server.
import { onMounted, onUnmounted } from 'vue'

export default {
  setup() {
    const onResize = () => { /* ... */ }
    onMounted(() => window.addEventListener('resize', onResize))
    onUnmounted(() => window.removeEventListener('resize', onResize))
  },
}

Warning

Lifecycle hooks must be called synchronously during setup. Calling onMounted inside an await, a setTimeout, or a .then() callback registers it against the wrong (or no) component instance and it will not fire.

5.6 Provide / Inject

Pass data to deeply nested descendants without prop-drilling.

// Ancestor
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)

// Any descendant
import { inject } from 'vue'
const theme = inject('theme', 'light') // second arg is the default
Function Purpose
provide(key, value) Provide a value to descendants. key can be a string or an InjectionKey<T> symbol for typing.
inject(key, default?, treatDefaultAsFactory?) Read a provided value, with an optional default.
hasInjectionContext() Check whether injection is currently possible.

Tip

For type-safe injection in TypeScript, create a typed key with InjectionKey:

import { type InjectionKey, provide, inject } from 'vue'
const ThemeKey: InjectionKey<Ref<string>> = Symbol('theme')
provide(ThemeKey, theme)   // value is type-checked
const theme = inject(ThemeKey)  // inferred as Ref<string> | undefined

5.7 Async components

Load a component lazily — its code is only fetched when it is first rendered.

import { defineAsyncComponent } from 'vue'

const AsyncDashboard = defineAsyncComponent(() =>
  import('./Dashboard.vue'),
)

Use the options form for loading/error states and timeouts:

const AsyncDashboard = defineAsyncComponent({
  loader: () => import('./Dashboard.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: LoadError,
  delay: 200,       // ms before showing the loading component
  timeout: 10000,   // ms before failing with the error component
})

Warning

If the loader resolves to something that is not a valid component, Vue throws Invalid async component load result. Make sure dynamic import() returns a module whose default export is a component (or return the component directly).


6. Template Syntax & Directives

Vue templates are HTML augmented with directives (v- prefixed attributes), interpolation ({{ }}), and bindings.

6.1 Text interpolation and binding

<template>
  <!-- text interpolation -->
  <span>{{ message }}</span>

  <!-- attribute binding (shorthand `:`) -->
  <img :src="imageUrl" :alt="caption" />

  <!-- event binding (shorthand `@`) -->
  <button @click="onClick">Go</button>

  <!-- two-way binding on form inputs -->
  <input v-model="name" />
</template>

6.2 Built-in directives

Directive Purpose
v-bind (:) Bind an attribute or prop to an expression.
v-on (@) Attach an event listener.
v-model Two-way binding on form elements and components.
v-if / v-else-if / v-else Conditionally render an element (removed from DOM when false).
v-show Toggle an element's CSS display (stays in DOM).
v-for Render a list from an array, object, number, or iterable.
v-slot (#) Define/consume named and scoped slots.
v-html Set an element's innerHTML.
v-text Set an element's text content.
v-pre Skip compilation for this element and its children.
v-once Render once and never update.
v-memo Memoize a subtree, re-rendering only when listed dependencies change.
v-cloak Hide un-compiled templates until Vue is ready (full build).

Caution

v-html renders raw HTML and can expose your app to cross-site scripting (XSS). Never use v-html on content that comes from users or any untrusted source. Prefer text interpolation, which is always escaped.

6.3 v-model modifiers and form binding

v-model works on <input>, <textarea>, <select>, and components. Built-in modifiers transform the bound value:

Modifier Effect
.lazy Sync on change instead of input.
.number Cast the input string to a number.
.trim Trim whitespace from the input.
<input v-model.trim.number="age" />

6.4 Event modifiers

v-on/@ supports modifiers for common event handling needs:

<form @submit.prevent="onSubmit">       <!-- event.preventDefault() -->
  <input @keyup.enter="search" />        <!-- only the Enter key -->
  <button @click.stop="onClick">Go</button> <!-- event.stopPropagation() -->
</form>

For programmatic handlers (render functions / JSX), Vue exports withModifiers and withKeys:

import { withModifiers, withKeys, h } from 'vue'

h('input', {
  onKeyup: withKeys(() => submit(), ['enter']),
  onClick: withModifiers(() => go(), ['stop', 'prevent']),
})

6.5 List rendering with v-for

<ul>
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }}: {{ item.name }}
  </li>
</ul>

Important

Always provide a :key with v-for so Vue can track identity across updates. Without keys, Vue falls back to in-place patching, which can produce incorrect results when list items have their own state.

Note

When iterating an object with v-for="(value, key) in obj", the key is typed as string (object keys are strings in JS). If you iterate with key/value and expect a numeric key, you must convert it yourself (related: issue #14910).


7. Built-in Components

Vue ships several components you can use directly in any template. The runtime-core components are available everywhere; Transition and TransitionGroup are DOM-specific (from @vue/runtime-dom, re-exported by vue).

Component Purpose
<Teleport> Render child content to a different place in the DOM (e.g. modals at <body>).
<Suspense> Coordinate nested async dependencies, showing fallback content while pending.
<KeepAlive> Cache toggled components so they keep state instead of being destroyed.
<Transition> Apply enter/leave animations to a single element or component.
<TransitionGroup> Animate insertion, removal, and reordering of list items.

7.1 Teleport

<template>
  <button @click="open = true">Open modal</button>
  <Teleport to="body">
    <div v-if="open" class="modal">
      Modal content rendered at &lt;body&gt;
      <button @click="open = false">Close</button>
    </div>
  </Teleport>
</template>

<Teleport> props: to (target selector or element), disabled (render in place when true), and defer (resolve the target after the current render, for targets rendered by Vue itself).

7.2 Suspense

<template>
  <Suspense>
    <template #default>
      <AsyncProfile />   <!-- a component with async setup() -->
    </template>
    <template #fallback>
      <div>Loading profile…</div>
    </template>
  </Suspense>
</template>

Note

<Suspense> is an experimental feature; its API may evolve. It works with components that use top-level await in setup or onServerPrefetch.

7.3 KeepAlive

<KeepAlive :include="['Home', 'Profile']" :max="10">
  <component :is="currentView" />
</KeepAlive>

<KeepAlive> props: include / exclude (component name matchers — string, RegExp, or array) and max (cap the number of cached instances, LRU-evicted). Cached components fire onActivated/onDeactivated instead of mount/unmount.

7.4 Transition

<template>
  <Transition name="fade">
    <p v-if="show">Hello</p>
  </Transition>
</template>

<style>
.fade-enter-active, .fade-leave-active { transition: opacity 0.3s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>

Key <Transition> props include name, mode ('in-out' / 'out-in'), appear, duration, and per-phase class overrides (enter-active-class, etc.). <TransitionGroup> additionally renders a real wrapper element (via tag) and applies a move-class for reorder animations.

Warning

<Transition> animates an element's presence; it does not animate visibility. Toggling visibility via v-show will not produce a transition the way display/opacity do (related: issue #2531). Animate opacity/transform instead.


8. Server-Side Rendering (SSR)

Vue can render components to HTML strings or streams on the server using the vue/server-renderer entry (the @vue/server-renderer package).

import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'

const app = createSSRApp({
  data: () => ({ msg: 'Hello from the server' }),
  template: `<div>{{ msg }}</div>`,
})

const html = await renderToString(app)
console.log(html) // <div>Hello from the server</div>
Function Output
renderToString(app, context?) Render to a complete HTML string (Promise).
renderToNodeStream(app, context?) Render to a Node.js Readable stream.
pipeToNodeWritable(app, context, writable) Pipe rendering into a Node.js Writable.
renderToWebStream(app, context?) Render to a Web ReadableStream.
pipeToWebWritable(app, context, writable) Pipe rendering into a Web WritableStream.
renderToSimpleStream(app, context, push) Render with a minimal push-based stream interface.

On the client, you "hydrate" the server-rendered markup instead of re-rendering it. Use createSSRApp (not createApp) so Vue attaches to existing DOM:

import { createSSRApp } from 'vue'
import App from './App.vue'

createSSRApp(App).mount('#app') // hydrates the server HTML in place

Important

The client app structure must match the server-rendered output exactly, or you get a hydration mismatch. Use useId() for any generated IDs and avoid rendering values that differ between server and client (like Date.now() or random numbers) without guarding them.

Tip

renderToStream is deprecated in favor of renderToNodeStream / renderToWebStream. Prefer the explicit stream variants for new code.


9. Custom Elements (Web Components)

Vue can compile any component into a standards-based custom element you can use outside Vue — in plain HTML, or in another framework.

import { defineCustomElement } from 'vue'
import MyWidget from './MyWidget.ce.vue'

const MyWidgetElement = defineCustomElement(MyWidget)
customElements.define('my-widget', MyWidgetElement)
<my-widget some-prop="value"></my-widget>
API Purpose
defineCustomElement(component, options?) Build a custom-element constructor from a Vue component.
defineSSRCustomElement(component, options?) Same, for server-rendered custom elements.
useHost() Access the host custom element from inside the component.
useShadowRoot() Access the component's shadow root.
VueElement The base class custom elements extend.

Note

A .ce.vue suffix (or customElement: true in your build tool) tells the SFC compiler to inject component styles into the shadow root instead of the document head, which is the correct behavior for custom elements.


10. Render Functions & JSX

When templates are not expressive enough, write render functions directly with h (hyperscript).

import { h, defineComponent, ref } from 'vue'

export default defineComponent({
  setup() {
    const count = ref(0)
    return () =>
      h('button', { onClick: () => count.value++ }, `Count: ${count.value}`)
  },
})
API Purpose
h(type, props?, children?) Create a vnode (the main render-function API).
mergeProps(...objects) Merge multiple props objects (class/style/handlers merged correctly).
cloneVNode(vnode, extraProps?) Clone a vnode with extra props.
isVNode(value) Check if a value is a vnode.
resolveComponent(name) Resolve a registered component by name in a render function.
resolveDirective(name) Resolve a registered directive by name.
withDirectives(vnode, directives) Attach custom directives to a vnode.
Fragment, Text, Comment, Static Special vnode type markers.

JSX is supported via the vue/jsx-runtime automatic runtime — configure your build's jsxImportSource to vue and write JSX that compiles to h calls.


11. Utility & Timing APIs

API Purpose
nextTick(callback?) Wait for the next DOM update flush. Returns a Promise.
version The Vue version string (import { version } from 'vue').
toDisplayString(value) The interpolation stringifier Vue uses for {{ }}.
normalizeClass(value) Normalize a class binding (string/array/object) to a class string.
normalizeStyle(value) Normalize a style binding to a style object/string.
useCssModule(name?) Access CSS Modules classes inside setup.
useCssVars(getter) Drive <style> CSS custom properties from reactive state.
import { ref, nextTick } from 'vue'

const count = ref(0)
count.value++
await nextTick()
// the DOM now reflects the new count

Tip

Use nextTick when you need to read the DOM after a reactive change has been applied — for example, measuring an element's size after its content changed.


12. How Vue Works (Concepts)

You can be productive with Vue without this section, but understanding the pipeline helps you debug and optimize.

  1. Reactivity — ref/reactive wrap your state in proxies. When code reads a reactive value during an effect (like a component's render), Vue records a dependency. When the value changes, Vue re-runs the dependent effects.
  2. Template compilation — Your <template> (or string template) is compiled into a render function that returns a tree of vnodes (virtual DOM nodes). With SFCs and a build step, this happens ahead of time; with the full build, it happens in the browser.
  3. Rendering & patching — On first render, Vue creates real DOM from the vnode tree. On updates, it produces a new vnode tree and patches only the differences into the real DOM.
  4. Compiler-informed fast paths — Vue's compiler annotates the output with hints (patch flags, static hoisting, block tracking) so the runtime skips work for parts of the tree that cannot change. This is why Vue templates are typically faster than hand-written render functions.

The packages map onto these stages: @vue/reactivity (state), @vue/compiler-dom + @vue/compiler-sfc (compilation), @vue/runtime-core + @vue/runtime-dom (rendering), and @vue/server-renderer (SSR). The umbrella vue package wires them together.


13. Troubleshooting

Error: Failed to mount app: mount target selector "#app" returned null.

Cause: app.mount('#app') was called with a CSS selector that matched no element — usually because the script ran before the target existed in the DOM, or the selector is wrong.

Solution: Ensure the target element exists before mounting (place the script at the end of <body> or load it as a module/deferred), and verify the selector matches your HTML's id.

Prevention: Use a module bundler entry that runs after DOM parse, or wrap mounting in a DOMContentLoaded listener for plain-script setups.

Error: Template element not found or is empty: #template

Cause: The full build was told to use an in-DOM template via a selector (template: '#template'), but no element with that id was found.

Solution: Confirm the referenced template element exists in the page and contains markup. For build-step apps, use SFCs instead of selector templates.

Error: invalid template option:

Cause: A template option was provided that is neither a string nor a DOM node.

Solution: Pass a string template or a real DOM element. If you meant to render a component, supply it as the root component to createApp, not as template.

Error: Component provided template option but runtime compilation is not supported

Cause: You are using the runtime-only build (the default vue import) but passed a string template that needs the compiler.

Solution: Either (a) use Single-File Components compiled by your build tool (recommended), or (b) switch to the full build by aliasing vue to vue/dist/vue.esm-bundler.js in your bundler config.

Prevention: Keep templates in .vue files so they compile at build time.

Warning: Template compilation error: Element is missing end tag

Cause: A template (compiled by the full build) contains malformed HTML, e.g. <div v-if> with no closing tag, or a self-closing native element that breaks parsing.

Solution: Fix the markup — close all elements. Vue prints a code frame pointing at the offending line.

Warning: Set operation on key "…" failed: target is readonly.

Cause: Code attempted to mutate a readonly/shallowReadonly proxy — commonly a prop (props are readonly) or an injected readonly value.

Solution: Do not mutate props directly. Emit an event to ask the parent to change the value, or use defineModel/v-model for two-way binding. For injected state, provide a setter function alongside the value.

Warning: Reactive Map/Set contains both the raw and reactive versions of the same object

Cause: The same object was used as a key/value in a reactive Map/Set in both its raw and reactive forms, which leads to inconsistent lookups.

Solution: Standardize on one form. Convert with toRaw() (or always use the reactive proxy) before inserting into a reactive collection.

Problem: Destructured reactive state stopped updating

Cause: Destructuring a reactive object copies primitive values, severing the reactive connection.

Solution: Wrap with toRefs(state) before destructuring, then use .value (or rely on template unwrapping).

Problem: Replacing a ref object or reactive variable breaks reactivity

Cause: Reassigning a whole reactive variable (state = reactive({...})) discards the tracked proxy. For arrays, replacing the array reference inside a ref is fine (list.value = [...]), but reassigning a reactive array variable is not.

Solution: Mutate the existing reactive object/array in place, or store the value in a ref and replace ref.value.

Problem: onMounted (or another hook) never fires

Cause: The hook was registered asynchronously (inside await, setTimeout, or a promise callback), so it was not associated with the active component instance.

Solution: Register all lifecycle hooks synchronously at the top level of setup.

Problem: Hydration mismatch warnings after SSR

Cause: The client-rendered tree differs from the server HTML — often from non-deterministic values (dates, random IDs) or invalid HTML nesting.

Solution: Use useId() for generated IDs, guard environment-specific rendering, and ensure valid HTML nesting. Enable __VUE_PROD_HYDRATION_MISMATCH_DETAILS__ to get specific diagnostics in production.

Problem: watch callback with immediate: true only runs once

Cause: With an empty or never-changing source, there is nothing to react to after the initial immediate run.

Solution: Watch a real reactive source, or use watchEffect if you want the callback to track whatever it reads.


14. FAQ

Q: Should I use ref or reactive? Use ref for primitives and when you want a single, reassignable container (x.value = ...). Use reactive for objects you mutate in place. Many teams standardize on ref everywhere for consistency, since ref can hold objects too.

Q: What's the difference between watch and watchEffect? watch takes explicit sources and gives you old + new values, and is lazy by default. watchEffect runs immediately and automatically re-runs when any reactive value it reads changes, but doesn't give you previous values.

Q: When do I need nextTick? Whenever you must read or act on the DOM after a reactive state change has been flushed to it — Vue batches DOM updates, so they aren't applied synchronously when you change state.

Q: Do I have to use Single-File Components? No. You can author components as plain objects with setup()/defineComponent, or with render functions/JSX. SFCs are the most ergonomic for most apps because they co-locate template, logic, and scoped CSS.

Q: How do I do two-way binding on a custom component? Use defineModel() in the child (<script setup>) and v-model in the parent. Under the hood this is a modelValue prop plus an update:modelValue event.

Q: Which build do I get by default from import ... from 'vue'? The runtime-only ESM bundler build (vue.runtime.esm-bundler.js). It excludes the template compiler because your SFC templates are compiled at build time.

Q: How do I enable the Options API tree-shaking? Set the bundler flag __VUE_OPTIONS_API__ to false. Only do this if no component (or dependency) relies on the Options API.

Q: Can I use Vue without a build step? Yes — load vue.global.js (or vue.esm-browser.js) from a CDN. These are full builds that include the compiler, so in-page templates work.

Q: How do I share logic between components? Write a composable: a function that uses the Composition API (ref, computed, watch, lifecycle hooks) and returns reactive state. Call it from any component's setup.

Q: How do I clean up side effects in a composable? Use onScopeDispose (works with any active effect scope) or a lifecycle hook like onUnmounted when inside a component. For per-run cleanup in a watcher, use onWatcherCleanup.

Q: Is <Suspense> production-ready? It is still marked experimental. It works today for async setup/onServerPrefetch, but treat its API as subject to change.

Q: What Node version do I need? Vue's tooling requires Node.js >= 20. The runtime itself runs in modern browsers (ES2016+) and in Node for SSR.


15. API Quick Reference

Reactivity (vue / @vue/reactivity): ref · shallowRef · isRef · unref · toRef · toRefs · toValue · proxyRefs · customRef · triggerRef · reactive · shallowReactive · readonly · shallowReadonly · isReactive · isReadonly · isProxy · isShallow · toRaw · markRaw · computed · watch · watchEffect · watchPostEffect · watchSyncEffect · onWatcherCleanup · effect · stop · effectScope · getCurrentScope · onScopeDispose

Component & lifecycle (vue): defineComponent · defineAsyncComponent · onBeforeMount · onMounted · onBeforeUpdate · onUpdated · onBeforeUnmount · onUnmounted · onActivated · onDeactivated · onErrorCaptured · onRenderTracked · onRenderTriggered · onServerPrefetch · provide · inject · hasInjectionContext

<script setup> macros: defineProps · defineEmits · defineExpose · defineOptions · defineSlots · defineModel · withDefaults

Composition helpers: useAttrs · useSlots · useModel · useTemplateRef · useId · useCssModule · useCssVars · getCurrentInstance

App & rendering: createApp · createSSRApp · nextTick · h · mergeProps · cloneVNode · isVNode · resolveComponent · resolveDirective · withDirectives · withModifiers · withKeys · version

Built-in components: Teleport · Suspense · KeepAlive · Transition · TransitionGroup

Custom elements: defineCustomElement · defineSSRCustomElement · useHost · useShadowRoot · VueElement

SSR (vue/server-renderer): renderToString · renderToNodeStream · pipeToNodeWritable · renderToWebStream · pipeToWebWritable · renderToSimpleStream