This was a small issue, but a useful one.
The link rendered correctly. The text was visible. Navigation worked.
But Vue still produced a warning related to the default slot.
The problem was not the Link component or routing.
It came from one argument passed to h(): component content was supplied as a raw string instead of a slot function.
The Code Looked Completely Natural
return h( Link, { href: item.url, }, item.label,)If you often use h() with native elements, this shape looks perfectly reasonable.
There is a type, some props, and child text.
But Link is not an HTML tag. It is a component.
That changes the meaning of the third argument.
A String Is Normal Child Content for a Native Element
return h( 'a', { href: item.url, }, item.label,)With 'a', Vue is creating a vnode for a native DOM element.
The third argument can be a string, number, another vnode, or an array of children.
Passing text as the third argument is therefore not inherently wrong.
What matters is what the first argument represents.
For a Component, Child Content Becomes a Slot
h( 'a', props, 'Open',) 'a'-> native element-> 'Open' is child text h( Link, props, 'Open',)A Vue component decides where the content passed to it should be rendered.
Vue represents that relationship with slots.
When manually creating a component vnode with h(), that content is therefore passed as a function.
The Fix Was Very Small
return h( Link, { href: item.url, }, () => item.label,)The text itself did not change.
Only the form in which it is passed to the component changed.
() => item.label represents the default slot and matches the component contract.
Vue no longer needs to normalize a value supplied in the wrong form at runtime.
The Default Slot Can Also Be Passed Explicitly
return h( Link, { href: item.url, }, { default: () => item.label, },)For a single default slot, the shorter function form is convenient.
The object form makes the model explicit: a component receives slot functions rather than ordinary native-element children.
It also becomes natural when a component exposes several named slots.
The Function Is More Than a Syntax Requirement
A function lets the child component invoke the slot when it needs to render that content.
It also allows Vue to track reactive dependencies closer to where the slot is consumed.
The warning is therefore not merely a cosmetic style complaint.
It indicates that component content was supplied in a form that does not match Vue slot semantics.
A Working UI Does Not Make the Warning Irrelevant
This is what made the bug interesting.
There was no blank screen, exception, or broken navigation.
Vue could still render the value, so the issue was easy to dismiss as a harmless warning.
But warnings like this expose a contract mismatch before it becomes a more confusing bug.
I prefer fixing the cause rather than accepting the warning merely because the current output looks correct.
I Check What the First h() Argument Actually Creates
Vue warning around h()? 1. Check the first argument. 'a''div''span'-> native element LinkButtonCustomComponent-> Vue component This Is Easy to Miss in Tables and Dynamic Renderers
{ key: 'name', cell: ({ row }) => h( Link, { href: row.url, }, () => row.name, ),}In Vue templates, slot syntax is usually natural enough that we rarely think about this distinction.
Manual h() calls are more common in column definitions, dynamic tables, component factories, and other programmatic renderers.
That is where it is easy to reuse a shape that was correct for 'span' or 'a' but is no longer correct for a component.
I Do Not Rewrite Every h() Call After One Warning
After fixing this warning, it is easy to overcorrect and start wrapping every third argument to h() in a function.
That is unnecessary.
For native elements, a string remains perfectly valid child content.
I change the places where the vnode represents a component and the third argument is actually slot content.
What I Check for This Kind of Vue Warning
My short checklist:
- What is the first h() argument: a native tag or a component?
- What is passed as the third argument?
- If it is a component, is its content provided as a slot function?
- Does the component need only the default slot or several named slots?
- Is the same incorrect call repeated across dynamic renderers?
- Does the warning disappear while behavior remains unchanged?
The Same h() Shape Does Not Mean the Same Child Semantics
This bug did not involve complicated reactivity or a large architectural problem.
It only required reading the h() contract correctly.
For a native element, the third argument may be simple child text.
For a component, the same visible content belongs to a slot and should be provided as a function.
A small syntax difference reflects an important semantic difference in what Vue is creating: a DOM element or a component.
When h() behaves unexpectedly, I first check whether its first argument is a native element or a component.



