---
title: "Vue 3.6 reaches release candidate: Vapor Mode is feature complete · vuejs.school"
description: "Vue 3.6.0-rc.1 declares Vapor Mode feature complete, and rc.2 already changed event handling. What the RC supports, what it leaves out, and how to test it."
canonical_url: "https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate"
meta:
  "og:description": "Vue 3.6.0-rc.1 declares Vapor Mode feature complete, and rc.2 already changed event handling. What the RC supports, what it leaves out, and how to test it."
  "og:image": "https://vuejs.school/_og/d/a_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGU%2Cdescription_~VnVlIDMuNi4wLXJjLjEgZGVjbGFyZXMgVmFwb3IgTW9kZSBmZWF0dXJlIGNvbXBsZXRlLCBhbmQgcmMuMiBhbHJlYWR5IGNoYW5nZWQgZXZlbnQgaGFuZGxpbmcuIFdoYXQgdGhlIFJDIHN1cHBvcnRzLCB3aGF0IGl0IGxlYXZlcyBvdXQsIGFuZCBob3cgdG8gdGVzdCBpdC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtMy02LXJlYWNoZXMtcmVsZWFzZS1jYW5kaWRhdGUi%2Cs_D_eJR3JgFo2bubds.png"
  "og:image:alt": "Vue 3.6 reaches release candidate: Vapor Mode is feature complete · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Vue 3.6 reaches release candidate: Vapor Mode is feature complete · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGU%2Cdescription_~VnVlIDMuNi4wLXJjLjEgZGVjbGFyZXMgVmFwb3IgTW9kZSBmZWF0dXJlIGNvbXBsZXRlLCBhbmQgcmMuMiBhbHJlYWR5IGNoYW5nZWQgZXZlbnQgaGFuZGxpbmcuIFdoYXQgdGhlIFJDIHN1cHBvcnRzLCB3aGF0IGl0IGxlYXZlcyBvdXQsIGFuZCBob3cgdG8gdGVzdCBpdC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtMy02LXJlYWNoZXMtcmVsZWFzZS1jYW5kaWRhdGUi%2Cs_D_eJR3JgFo2bubds.png"
  "twitter:image:alt": "Vue 3.6 reaches release candidate: Vapor Mode is feature complete · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## What Vapor Mode is and how stable the RC is

Vapor Mode is a compilation mode for single-file components. A Vapor component compiles to code that creates its DOM directly and updates each binding when its reactive value changes, with no virtual DOM tree to build and compare. The notes give two goals, a smaller baseline bundle and better performance, and say Vapor has shown the same level of performance as Solid and Svelte 5 in third-party benchmarks.

The wording on stability changed between phases. The beta notes called Vapor "feature-complete in Vue 3.6 beta, but is still considered unstable". The RC notes say "Vapor Mode is feature-complete in Vue 3.6 RC" and drop the word "unstable". The two recommended uses did not change:

- a performance-sensitive page inside an existing app;
- a small new app built entirely in Vapor.

## Turn on Vapor for a component or a whole app

Vapor is still 100 percent opt-in, one component at a time. The RC notes list three ways to mark a component. The first is a `vapor` attribute on `<script setup>`:

*src/components/StockTicker.vue*

```
<script setup vapor lang="ts">
import { ref } from 'vue'

const price = ref(101.5)
</script>

<template>
  <p>Price: {{ price }}</p>
  <button type="button" @click="price += 0.5">Up</button>
</template>
```

The second, `<script vapor>`, is a shorthand for `<script setup vapor>`. The third, a `vapor` attribute on `<template>`, turns on Vapor compilation for the whole file, which is how a component with no script opts in. The Options API is not supported in any form, so a component with `export default { data() {} }` cannot be a Vapor component.

A Vapor component needs a host. An app made only of Vapor components uses `createVaporApp`, which the notes say avoids pulling in the virtual DOM runtime and is where the drastically smaller baseline bundle comes from:

*src/main.ts*

```
import { createVaporApp } from 'vue'
import App from './App.vue'

createVaporApp(App).mount('#app')
```

An existing app keeps `createApp` and installs the interop plugin, which lets Vapor and virtual DOM components sit inside each other:

*src/main.ts*

```
import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'

createApp(App).use(vaporInteropPlugin).mount('#app')
```

Components written as render functions or in JSX stay virtual DOM components, so a Vapor app that uses them needs the plugin too, which brings the virtual DOM runtime back.

The interop covers props, events and slots passed between the two kinds of component. In the notes' words, it "does not yet account for all possible edge cases", and the example they give is a virtual DOM component library used inside Vapor. Their advice is the same as in the beta: give each rendering mode its own distinct region of the app, and avoid nesting the two back and forth.

## What Vapor Mode does not support

The RC notes say Vapor supports a subset of Vue by design, and give the rule behind it: features that depend on virtual DOM nodes or on the component public instance proxy are not available. The public instance proxy is the object behind `this` in the Options API, and what a template ref on a virtual DOM component returns. The notes list these as unsupported or not applicable in Vapor:

- The Options API.
- `app.config.globalProperties`.
- `getCurrentInstance()`, which returns `null` in a Vapor component.
- `@vue:xxx` per-element lifecycle events.
- `v-memo`.
- Component template refs that expose `$el`, `$props`, `$attrs`, `$slots` or `$refs`.

Two of these were not in the beta.1 notes. `v-memo` exists to skip re-creating virtual DOM nodes for a subtree, and a Vapor component has no virtual DOM nodes to skip. If a list relies on `v-memo` for speed, that component either stays a virtual DOM component or drops the directive when it moves to Vapor.

The template ref change means that code such as `childRef.value.$el`, written to reach a child's root element, stops working. Expose what the parent needs with `defineExpose` instead.

## rc.2 makes event delegation opt-in

Four days after rc.1, rc.2 reversed a default. Vapor used to delegate supported events to `document`: one listener at the top of the page found the right handler for each click. That differed from normal Vue, where a listener sits on the element itself, and it had a visible cost. An ancestor that called `stopPropagation()` could stop a child's handler from ever running.

The [rc.2 changelog](https://github.com/vuejs/core/blob/v3.6.0-rc.2/CHANGELOG.md) lists the change under breaking changes: listeners are now attached directly to elements by default, matching virtual DOM mode and the browser. Delegation becomes an opt-in optimisation, through a new Vapor-only `.delegate` modifier on supported static events:

*src/components/ProductGrid.vue*

```
<script setup vapor lang="ts">
const emit = defineEmits<{ add: [sku: string] }>()
const skus = ['A-100', 'A-200', 'A-300']

function onAdd(sku: string) {
  emit('add', sku)
}
</script>

<template>
  <button v-for="sku in skus" :key="sku" type="button" @click.delegate="onAdd(sku)">
    Add {{ sku }}
  </button>
</template>
```

With `.delegate`, Vue adds one `click` listener on `document` instead of one per button, and the old trade-off comes back for these buttons: an ancestor that calls `stopPropagation()` stops their handlers.

The `compilerOptions.eventDelegation` setting was removed in the same release. If your build config sets it, delete it, and put `.delegate` on the individual listeners where one shared listener is worth having, such as long lists of buttons.

## Slots and custom directives work differently

In Vapor, calling `slots.default()` is not a harmless look at what a slot contains. It runs the slot: it can create DOM nodes, register reactive effects, and claim server-rendered markup during hydration. Code that calls the slot only to decide whether to show a fallback is exactly what the notes warn against:

*src/components/EmptyState.vue**Breaks***

```
<script setup vapor lang="ts">
import { useSlots } from 'vue'

const slots = useSlots()
const showFallback = !slots.default?.()
</script>

<template>
  <div v-if="showFallback">Nothing here yet</div>
</template>
```

The notes' advice is to leave slot rendering to the template. Elements inside `<slot>` are fallback content, rendered when the parent passes nothing for the slot. With no script left, the `vapor` attribute moves to `<template>`, so the component stays a Vapor component:

*src/components/EmptyState.vue**Works***

```
<template vapor>
  <slot>
    <div>Nothing here yet</div>
  </slot>
</template>
```

Custom directives have a different interface in Vapor too, the same one the notes have described since the alpha: a function that receives the element and a getter for the value, and may return a cleanup function.

## The reactivity rewrite reaches every app

The RC notes' second headline is unchanged since the beta and reaches every 3.6 app, Vapor or not. `@vue/reactivity` has been refactored on top of [alien-signals](https://github.com/stackblitz/alien-signals), a standalone signals library, and the notes say this significantly improves the reactivity system's performance and memory usage. The notes list no change to the public API: you call `ref`, `reactive`, `computed`, `watch` and `watchEffect` as before.

That makes it the part of the RC most worth testing, because your existing test suite covers it without a change to a single component. Code that leaned on undocumented timing, such as the order two watchers fire in, is where a difference would most likely show.

## Try the RC in a branch

A release candidate means the feature set is decided and the work left is fixing. It does not mean nothing will change, and rc.2 proved that within four days. So treat it as a test target, not a dependency. From the root of an existing project:

```
git switch -c try-vue-3-6
npm pkg set overrides.vue='$vue'
npm install vue@3.6.0-rc.2 --save-exact
npm run type-check && npm run test:unit
```

What the two middle lines do:

- `npm pkg set` adds `"overrides": { "vue": "$vue" }` to `package.json`. `@vitejs/plugin-vue`, Pinia and Vue Router each declare the Vue versions they work with as a range such as `^3.2.25`, and npm does not count a pre-release as inside such a range. Without the [override](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#overrides), npm stops with an `ERESOLVE` peer conflict, on this install or the next one. `$vue` stands for the version in your own dependencies, so every package that asks for Vue accepts the RC. The single quotes keep the shell from reading `$vue` as a variable.
- `--save-exact` writes `3.6.0-rc.2` into `package.json`, so the next RC arrives only when you choose it. Without it, npm saves `^3.6.0-rc.2`, a range a later install can resolve to a newer RC.

npm's error message suggests `--legacy-peer-deps` instead. Avoid it: npm then ignores peer dependencies completely, and Pinia 4 imports one of its peers, `@vue/devtools-api`, which can go missing.

Then test in two rounds:

1. **Without Vapor.** The last command runs the type check and the unit tests, under the script names create-vue uses (change them to yours). With no Vapor components yet, this tests the reactivity rewrite on its own.
2. **With one Vapor page.** Pick one page that measurement says is slow and that uses only `<script setup>`. Convert its components, install `vaporInteropPlugin` in `src/main.ts`, then run the checks and measure the page again.

**Check it stuck**3 questions from the Performance and Vapor Mode quiz.

[**Practice now**](https://vuejs.school/learn/advanced/performance-and-vapor-mode?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Release notes · 7 min read**Vue 3.6 and Vapor Mode: where things stand**](https://vuejs.school/community/blog/vue-3-6-and-vapor-mode-where-things-stand) [Deep dive · 7 min read**Vapor Mode, explained without the hype**](https://vuejs.school/community/blog/vapor-mode-explained-without-the-hype)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-07-23","description":"Vue 3.6.0-rc.1 declares Vapor Mode feature complete, and rc.2 already changed event handling. What the RC supports, what it leaves out, and how to test it.","name":"Vue 3.6 reaches release candidate: Vapor Mode is feature complete","url":"https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate","breadcrumb":{"@id":"https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-07-23","description":"Vue 3.6.0-rc.1 declares Vapor Mode feature complete, and rc.2 already changed event handling. What the RC supports, what it leaves out, and how to test it.","headline":"Vue 3.6 reaches release candidate: Vapor Mode is feature complete","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGU%2Cdescription_~VnVlIDMuNi4wLXJjLjEgZGVjbGFyZXMgVmFwb3IgTW9kZSBmZWF0dXJlIGNvbXBsZXRlLCBhbmQgcmMuMiBhbHJlYWR5IGNoYW5nZWQgZXZlbnQgaGFuZGxpbmcuIFdoYXQgdGhlIFJDIHN1cHBvcnRzLCB3aGF0IGl0IGxlYXZlcyBvdXQsIGFuZCBob3cgdG8gdGVzdCBpdC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtMy02LXJlYWNoZXMtcmVsZWFzZS1jYW5kaWRhdGUi%2Cs_D_eJR3JgFo2bubds.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/vue-3-6-reaches-release-candidate#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Vue 3.6 reaches release candidate: Vapor Mode is feature complete","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGU%2Cdescription_~VnVlIDMuNi4wLXJjLjEgZGVjbGFyZXMgVmFwb3IgTW9kZSBmZWF0dXJlIGNvbXBsZXRlLCBhbmQgcmMuMiBhbHJlYWR5IGNoYW5nZWQgZXZlbnQgaGFuZGxpbmcuIFdoYXQgdGhlIFJDIHN1cHBvcnRzLCB3aGF0IGl0IGxlYXZlcyBvdXQsIGFuZCBob3cgdG8gdGVzdCBpdC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtMy02LXJlYWNoZXMtcmVsZWFzZS1jYW5kaWRhdGUi%2Cs_D_eJR3JgFo2bubds.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGUgwrcgdnVlanMuc2Nob29s%2Cc_OgCard.takumi%2Ctitle_~VnVlIDMuNiByZWFjaGVzIHJlbGVhc2UgY2FuZGlkYXRlOiBWYXBvciBNb2RlIGlzIGZlYXR1cmUgY29tcGxldGU%2Cdescription_~VnVlIDMuNi4wLXJjLjEgZGVjbGFyZXMgVmFwb3IgTW9kZSBmZWF0dXJlIGNvbXBsZXRlLCBhbmQgcmMuMiBhbHJlYWR5IGNoYW5nZWQgZXZlbnQgaGFuZGxpbmcuIFdoYXQgdGhlIFJDIHN1cHBvcnRzLCB3aGF0IGl0IGxlYXZlcyBvdXQsIGFuZCBob3cgdG8gdGVzdCBpdC4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA3IG1pbiByZWFk%2Ctint_lilac%2Cp_Ii9jb21tdW5pdHkvYmxvZy92dWUtMy02LXJlYWNoZXMtcmVsZWFzZS1jYW5kaWRhdGUi%2Cs_D_eJR3JgFo2bubds.png"}]}
```