Skip to content

Feature Request: Automatically wrap non-function default slot children like SFC compiler does, to silence "Non-function value encountered" warning in JSX/TSX #736

@busy-dog

Description

@busy-dog

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions