Extending RouterLink | Vue Router - Custom Navigation Components ๐จ โ
Beyond basic links - Create sophisticated navigation elements with complete control over appearance and behavior!
๐ฏ Overview โ
While RouterLink covers most common use cases, sometimes you need more flexibility. Vue Router provides multiple ways to extend and customize link behavior, from simple prop configurations to complete custom components using the Composition API.
๐ Extension Approaches โ
- Props Configuration - Basic customization via props
- v-slot API - Render function control
- Custom Components - Complete behavioral control
- useLink() Composable - Programmatic link logic
๐ก When to Extend RouterLink โ
๐จ Custom UI Requirements โ
Non-standard Link Elements
- Buttons that act as navigation
- Card components that are clickable
- Complex navigation menus
- Custom hover and active states
Advanced Behavior
- External link handling
- Analytics integration
- Permission-based navigation
- Progressive enhancement
๐ง Technical Scenarios โ
<!-- Custom navigation button -->
<AppLink
to="/dashboard"
variant="button"
size="large"
icon="dashboard"
>
Dashboard
</AppLink>
<!-- External link with security -->
<AppLink
to="https://external.com"
external
noopener
security="high"
>
External Site
</AppLink>๐ Basic Extension Techniques โ
Method 1: Props Configuration โ
The simplest way to customize RouterLink is through its props:
<template>
<RouterLink
to="/custom"
active-class="text-blue-600 font-bold"
exact-active-class="border-b-2 border-blue-600"
custom
v-slot="{ href, navigate, isActive }"
>
<a
:href="href"
@click="navigate"
:class="[
'px-4 py-2 rounded transition-colors',
isActive ? 'bg-blue-100 text-blue-800' : 'text-gray-600 hover:bg-gray-100'
]"
>
Custom Styled Link
</a>
</RouterLink>
</template>Method 2: Global Configuration โ
Configure default behavior for all RouterLink instances:
const router = createRouter({
// ... other options
linkActiveClass: 'nav-link-active',
linkExactActiveClass: 'nav-link-exact-active',
})๐ง Advanced Custom Components โ
Creating AppLink Component โ
Build a reusable AppLink component that handles various scenarios:
<template>
<component
:is="tag"
v-bind="linkAttributes"
@click="handleClick"
:class="computedClasses"
>
<slot />
</component>
</template>
<script setup>
import { computed } from 'vue'
import { useLink } from 'vue-router'
const props = defineProps({
to: [String, Object],
replace: Boolean,
external: Boolean,
disabled: Boolean,
variant: {
type: String,
default: 'link',
validator: (value) => ['link', 'button', 'card'].includes(value)
}
})
const { navigate, href, isActive, isExactActive } = useLink(props)
const tag = computed(() => props.external ? 'a' : 'button')
const isExternal = computed(() =>
typeof props.to === 'string' && props.to.startsWith('http')
)
const linkAttributes = computed(() => {
if (props.external || isExternal.value) {
return {
href: props.to,
target: '_blank',
rel: 'noopener noreferrer'
}
}
return {
href: href.value,
role: 'link'
}
})
const computedClasses = computed(() => [
'app-link',
`app-link--${props.variant}`,
{
'app-link--active': isActive.value,
'app-link--exact-active': isExactActive.value,
'app-link--disabled': props.disabled
}
])
function handleClick(event) {
if (props.disabled) {
event.preventDefault()
return
}
if (!props.external && !isExternal.value) {
event.preventDefault()
navigate(event)
}
// Emit custom event
emit('navigate', { to: props.to, event })
}
</script>
<style scoped>
.app-link {
transition: all 0.3s ease;
cursor: pointer;
}
.app-link--link {
text-decoration: none;
color: #007bff;
}
.app-link--button {
padding: 8px 16px;
border: 1px solid #007bff;
border-radius: 4px;
background: white;
color: #007bff;
}
.app-link--button:hover {
background: #007bff;
color: white;
}
.app-link--card {
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.app-link--card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
transform: translateY(-2px);
}
.app-link--active {
font-weight: bold;
}
.app-link--exact-active {
border-color: #007bff;
}
.app-link--disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>๐ ๏ธ Practical Examples โ
Example 1: Navigation Menu Component โ
<template>
<nav class="app-navigation">
<AppLink
v-for="item in navigationItems"
:key="item.to"
:to="item.to"
:variant="item.variant"
:icon="item.icon"
class="nav-item"
>
{{ item.label }}
</AppLink>
</nav>
</template>
<script setup>
import { ref } from 'vue'
const navigationItems = ref([
{
to: '/',
label: 'Home',
variant: 'link',
icon: 'home'
},
{
to: '/products',
label: 'Products',
variant: 'link',
icon: 'shopping-bag'
},
{
to: '/about',
label: 'About Us',
variant: 'button',
icon: 'info'
},
{
to: '/contact',
label: 'Contact',
variant: 'card',
icon: 'mail'
}
])
</script>
<style scoped>
.app-navigation {
display: flex;
gap: 1rem;
padding: 1rem;
background: #f8f9fa;
}
.nav-item {
flex: 1;
text-align: center;
}
</style>Example 2: Breadcrumb Navigation โ
<template>
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li v-for="(crumb, index) in breadcrumbs" :key="index">
<AppLink
:to="crumb.to"
:disabled="!crumb.to"
class="breadcrumb-item"
:class="{ 'breadcrumb-item--current': !crumb.to }"
>
{{ crumb.label }}
</AppLink>
<span
v-if="index < breadcrumbs.length - 1"
class="breadcrumb-separator"
>
/
</span>
</li>
</ol>
</nav>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const breadcrumbs = computed(() => {
const paths = route.path.split('/').filter(Boolean)
const crumbs = []
let accumulatedPath = ''
paths.forEach((path, index) => {
accumulatedPath += `/${path}`
const isLast = index === paths.length - 1
crumbs.push({
label: path.charAt(0).toUpperCase() + path.slice(1),
to: isLast ? null : accumulatedPath
})
})
// Add home as first breadcrumb
crumbs.unshift({
label: 'Home',
to: '/'
})
return crumbs
})
</script>
<style scoped>
.breadcrumb ol {
display: flex;
align-items: center;
gap: 0.5rem;
list-style: none;
padding: 0;
margin: 0;
}
.breadcrumb-item {
text-decoration: none;
color: #6c757d;
padding: 0.25rem 0.5rem;
}
.breadcrumb-item:hover:not(.breadcrumb-item--current) {
color: #007bff;
}
.breadcrumb-item--current {
color: #495057;
font-weight: bold;
cursor: default;
}
.breadcrumb-separator {
color: #6c757d;
}
</style>๐ง useLink() Composable โ
For maximum control, use the useLink() composable:
<template>
<button
@click="handleNavigation"
:class="buttonClasses"
:disabled="isDisabled"
>
<slot />
</button>
</template>
<script setup>
import { computed } from 'vue'
import { useLink } from 'vue-router'
const props = defineProps({
to: [String, Object],
replace: Boolean,
disabled: Boolean
})
const { navigate, isActive, isExactActive } = useLink(props)
const isDisabled = computed(() => props.disabled || isActive.value)
const buttonClasses = computed(() => [
'custom-button',
{
'custom-button--active': isActive.value,
'custom-button--exact-active': isExactActive.value,
'custom-button--disabled': isDisabled.value
}
])
function handleNavigation(event) {
if (!isDisabled.value) {
// Add custom logic before navigation
trackAnalytics(props.to)
// Execute navigation
navigate(event)
}
}
function trackAnalytics(to) {
// Send analytics data
console.log('Navigating to:', to)
}
</script>๐ Best Practices โ
โ Do โ
- Consistent Styling: Maintain visual consistency across custom links
- Accessibility: Ensure proper ARIA attributes and keyboard navigation
- Performance: Use lazy loading for complex custom components
- Testing: Test custom link behavior thoroughly
โ Don't โ
- Reinvent Wheel: Use built-in features when they suffice
- Break Semantics: Maintain proper HTML semantics
- Ignore SEO: Consider search engine optimization for important links
- Overcomplicate: Keep customizations simple and maintainable
๐ Related Features โ
- Route Meta Fields: Add metadata for custom link behavior
- Navigation Guards: Control access to custom links
- Scroll Behavior: Customize scrolling for custom navigation
- Transition Effects: Add animations to custom link interactions
๐ก Pro Tip
Combine custom RouterLink extensions with Vue's provide/inject API to create a consistent navigation system that can be easily customized across your entire application!
Ready to build sophisticated navigation? Start extending
RouterLinkto create custom navigation experiences that perfectly match your application's needs! ๐จ