# Vue <RouterLink>
with disabled
state
Vue Router is awesome, but we all know that <RouterLink>
should have a disabled
state.
Today, I've built my own <AppLink>
together with 🤖 Gemini (gemini-2.5-pro-exp-03-25)
, and you can have it as a treat.
It is awesome and it has everything you need:
disabled
state.aria-disabled="true"
on an inactive link.- Optional tooltip with
v-tippy
! I was usingprops.iCanHasCheeseburger ? '' : 'opacity-20 pointer-events-none'
with plainRouterLink
before, but it was incompatible withv-tippy
(it need events). So, I had to build this.
Enjoy!
import {
defineComponent,
h,
type DefineComponent,
type PropType,
type HTMLAttributes,
type VNodeProps,
withDirectives,
resolveDirective,
} from 'vue'
import { RouterLink, type RouterLinkProps } from 'vue-router'
// Combine RouterLinkProps with potential HTML attributes and Vue-specific props
type CombinedProps = RouterLinkProps & VNodeProps & HTMLAttributes;
type AppLinkProps = Omit<CombinedProps, 'disabled'> & {
// Omit original HTML disabled, use ours
/**
* Whether the link is disabled. If true, renders a `<span>` instead of a link
* and prevents navigation. Adds specific classes, aria-disabled attribute, and a tooltip.
*/
disabled?: boolean;
/**
* Tooltip text to display when the link is disabled.
*/
disabledTooltip?: string;
};
export default defineComponent({
inheritAttrs: false, // Disable default attribute inheritance, handle manually
props: {
// @ts-expect-error It's okay, RouterLink props are included
...RouterLink.props,
disabled: {
type: Boolean as PropType<AppLinkProps['disabled']>,
default: false,
},
disabledTooltip: {
type: String as PropType<AppLinkProps['disabledTooltip']>,
default: undefined,
},
},
setup (props, { slots, attrs }) {
return () => {
if (props.disabled) {
// Props for the disabled span
const spanProps: HTMLAttributes & VNodeProps = {
...attrs, // Spread non-prop attributes first
class: [attrs.class, 'cursor-default opacity-20'], // Merge classes
'aria-disabled': true,
}
// Create the base span VNode
const spanVNode = h('span', spanProps, slots)
// Resolve the v-tippy directive
const tippyDirective = resolveDirective('tippy')
// Apply the directive if found
if (tippyDirective && props.disabledTooltip) {
return withDirectives(spanVNode, [
[
tippyDirective,
{ content: props.disabledTooltip, placement: 'top' },
],
])
} else {
return spanVNode // Return the span without the tooltip
}
} else {
// Props for the active RouterLink
// Type assertion needed as props are RouterLink specific + attrs
const routerLinkProps = { ...attrs, ...props } as CombinedProps
return h(RouterLink, routerLinkProps, slots)
}
}
},
}) as DefineComponent<AppLinkProps>