Related plugins
Description
Describe the feature you'd like
In Vue 3 SFC (.vue files with <template>), the compiler automatically wraps default slot content into a function slot:
<!-- SFC: no warning -->
<WDialog>
<div>Hello</div>
</WDialog>
This compiles roughly to h(WDialog, null, { default: () => [h('div', 'Hello')] }) — silent and performant.
However, in pure JSX/TSX with @vitejs/plugin-vue-jsx, the same pattern:
<WDialog>
<div>Hello</div>
</WDialog>
transpiles to h(WDialog, null, [h('div', 'Hello')]) (non-function array), triggering the Vue runtime warning:
[Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.
This forces users to write verbose function wrappers everywhere:
<WDialog>{() => <div>Hello</div>}</WDialog>
Which feels redundant and less ergonomic compared to SFC.
Why this matters
Inconsistency: SFC users get automatic optimization; JSX/TSX users get warnings and have to manually wrap.
DX degradation: Many developers (especially coming from React or Vue 2) find it surprising and verbose.
Common pain point: This warning appears frequently in UI libraries (Dialog/Modal/Button with children), third-party components, and even simple static text.
Proposed solution
Add an optional config to @vitejs/plugin-vue-jsx (or make it default behavior) that mimics SFC compiler:
When children is a non-function value (VNode[], string, number, etc.), automatically wrap it as { default: () => children }.
This silences the warning without changing runtime behavior.
Config example:
// vite.config.ts
vueJsx({
autoWrapDefaultSlot: true, // default: false (for backward compat), or true in future major
// or more granular: autoWrapStaticChildren: true
})
Or
Always do it (breaking change in major version), with opt-out.
This keeps the "prefer function slots" philosophy (warn in docs), but reduces friction for common cases.
Environment info
Vue: 3.x
Vite: 8.0
@vitejs/plugin-vue-jsx: latest
Suggested solution
Alternative
Global app.config.warnHandler filter → temporary hack, not a solution.
Manual wrapping everywhere → verbose, error-prone, defeats DX.
Change Vue core to auto-wrap → unlikely, as Vue team prefers explicit.
ESLint rule to enforce function slots → good long-term, but doesn't fix the inconsistency.
Additional context
Related discussions:
Vue core issue: vuejs/core#11331
Stack Overflow: https://stackoverflow.com/questions/69875273/non-function-value-encountered-for-default-slot-in-vue-3-composition-api-comp
Many users suppress via warnHandler, but better to fix at plugin level.
Thanks for considering this! Would love to hear thoughts from maintainers.
Validations
Related plugins
plugin-vue
plugin-vue-jsx
Description
Describe the feature you'd like
In Vue 3 SFC (.vue files with
<template>), the compiler automatically wraps default slot content into a function slot:This compiles roughly to h(WDialog, null, { default: () => [h('div', 'Hello')] }) — silent and performant.
However, in pure JSX/TSX with @vitejs/plugin-vue-jsx, the same pattern:
transpiles to h(WDialog, null, [h('div', 'Hello')]) (non-function array), triggering the Vue runtime warning:
This forces users to write verbose function wrappers everywhere:
Which feels redundant and less ergonomic compared to SFC.
Why this matters
Inconsistency: SFC users get automatic optimization; JSX/TSX users get warnings and have to manually wrap.
DX degradation: Many developers (especially coming from React or Vue 2) find it surprising and verbose.
Common pain point: This warning appears frequently in UI libraries (Dialog/Modal/Button with children), third-party components, and even simple static text.
Proposed solution
Add an optional config to @vitejs/plugin-vue-jsx (or make it default behavior) that mimics SFC compiler:
When children is a non-function value (VNode[], string, number, etc.), automatically wrap it as { default: () => children }.
This silences the warning without changing runtime behavior.
Config example:
Or
Always do it (breaking change in major version), with opt-out.
This keeps the "prefer function slots" philosophy (warn in docs), but reduces friction for common cases.
Environment info
Vue: 3.x
Vite: 8.0
@vitejs/plugin-vue-jsx: latest
Suggested solution
Alternative
Global app.config.warnHandler filter → temporary hack, not a solution.
Manual wrapping everywhere → verbose, error-prone, defeats DX.
Change Vue core to auto-wrap → unlikely, as Vue team prefers explicit.
ESLint rule to enforce function slots → good long-term, but doesn't fix the inconsistency.
Additional context
Related discussions:
Vue core issue: vuejs/core#11331
Stack Overflow: https://stackoverflow.com/questions/69875273/non-function-value-encountered-for-default-slot-in-vue-3-composition-api-comp
Many users suppress via warnHandler, but better to fix at plugin level.
Thanks for considering this! Would love to hear thoughts from maintainers.
Validations