vuejs/vue-router · info

In Vue Router 4, the v-slot API will by default wrap its con

Error message

In Vue Router 4, the v-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:
<router-link v-slot="{ navigate, href }" custom></router-link>

What it means

When <router-link> receives a scoped slot with a number of root children other than exactly one, it cannot render them as a single replacement element, so in dev it warns that it will wrap the content in a <span> instead. The library then renders h('span', {}, scopedSlot) to keep the DOM valid.

Source

Thrown at src/components/link.js:116

      on[this.event] = handler
    }

    const data: any = { class: classes }

    const scopedSlot =
      !this.$scopedSlots.$hasNormal &&
      this.$scopedSlots.default &&
      this.$scopedSlots.default({
        href,
        route,
        navigate: handler,
        isActive: classes[activeClass],
        isExactActive: classes[exactActiveClass]
      })

    if (scopedSlot) {
      if (process.env.NODE_ENV !== 'production' && !this.custom) {
        !warnedCustomSlot && warn(false, 'In Vue Router 4, the v-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:\n<router-link v-slot="{ navigate, href }" custom></router-link>\n')
        warnedCustomSlot = true
      }
      if (scopedSlot.length === 1) {
        return scopedSlot[0]
      } else if (scopedSlot.length > 1 || !scopedSlot.length) {
        if (process.env.NODE_ENV !== 'production') {
          warn(
            false,
            `<router-link> with to="${
              this.to
            }" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
          )
        }
        return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
      }
    }

    if (process.env.NODE_ENV !== 'production') {

View on GitHub (pinned to 680ccc68c5)

Solutions

  1. Wrap the slot content in a single root element (e.g. a <span> or <div>).
  2. Remove empty/comment-only content from the v-slot body.
  3. Use the custom prop with exactly one functional child or restructure to a single component root.

Example fix

// before (two roots)
<router-link to="/x" custom v-slot="{ href, navigate }"><span>A</span><span>B</span></router-link>
// after (single root)
<router-link to="/x" custom v-slot="{ href, navigate }"><span><span>A</span><span>B</span></span></router-link>
Defensive patterns

Strategy: validation

Validate before calling

// template-time check: use custom whenever v-slot is present on router-link
// e.g. ESLint rule idea: flag <router-link v-slot without custom

Prevention

When it happens

Trigger: <router-link to="..." v-slot="{ ... }"> containing zero children (empty slot) or more than one root node (e.g. two sibling elements or text plus an element), in dev mode with the functional/normal slot-length check (scopedSlot.length).

Common situations: Adding a comment node or whitespace-producing extra root inside the v-slot; components returning fragments (multiple roots); accidentally rendering an empty v-slot body.

Related errors


AI-assisted analysis of vuejs/vue-router@680ccc68c5 (2026-09-02). Data as JSON: /api/errors/7db705e914c2af77. Report an issue: GitHub.