vuejs/vue-router · info

<router-link>'s tag prop is deprecated and has been removed…

Error message

<router-link>'s tag prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props

What it means

This is a dev-only deprecation warning: the event prop of <router-link> (which changed the click event, e.g. event="mouseover") is removed in Vue Router 4. The v-slot API replaces it by exposing navigate so you can bind navigation to any event yourself. Fires once (warnedEventProp flag) when event is present in propsData.

Solutions

  1. Remove the event prop and use v-slot with custom, calling navigate in whatever event handler you want.
  2. For default behavior, rely on the built-in click handling and drop the prop entirely.
  3. For non-click navigation triggers, call router.push directly from your own handler.

Example fix

// before
<router-link to="/about" event="mouseover">About</router-link>
// after
<router-link to="/about" custom v-slot="{ href, navigate }">
  <a :href="href" @mouseover="navigate">About</a>
</router-link>
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Passing event="..." (or an array of events) to <router-link> on a vue-router 3.x next/4 migration build in dev mode.

Common situations: Code from vue-router 3 that made links navigate on hover or custom events; migration to vue-router 4 without updating router-link usage.

Related errors


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

Appendix: source

Thrown at src/components/link.js:136

      }
      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') {
      if ('tag' in this.$options.propsData && !warnedTagProp) {
        warn(
          false,
          `<router-link>'s tag prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
        )
        warnedTagProp = true
      }
      if ('event' in this.$options.propsData && !warnedEventProp) {
        warn(
          false,
          `<router-link>'s event prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
        )
        warnedEventProp = true
      }
    }

    if (this.tag === 'a') {
      data.on = on
      data.attrs = { href, 'aria-current': ariaCurrentValue }
    } else {

View on GitHub (pinned to 680ccc68c5)