Active Links | Vue Router - Highlight Current Navigation ๐ โ
Visual navigation feedback - Show users where they are with intelligent active link highlighting!
๐ฏ Overview โ
Active links provide visual feedback to users by highlighting the current page or section in navigation menus. Vue Router makes this easy with built-in active state management that works seamlessly with your routing configuration.
๐ Key Benefits โ
- User Orientation: Help users understand their current location
- Visual Feedback: Provide clear navigation cues
- Professional Polish: Enhanced user experience
- Easy Implementation: Simple configuration with powerful results
๐ก When to Use Active Links โ
๐จ Navigation Menus โ
Main Navigation
- Header navigation bars
- Sidebar menus
- Breadcrumb trails
- Tabbed interfaces
Complex Applications
- Multi-level navigation
- Nested route highlighting
- Dynamic active states based on route parameters
๐ง Basic Implementation โ
<template>
<nav>
<RouterLink to="/" active-class="active">Home</RouterLink>
<RouterLink to="/about" active-class="active">About</RouterLink>
<RouterLink to="/contact" active-class="active">Contact</RouterLink>
</nav>
</template>
<style>
.active {
color: #007bff;
font-weight: bold;
border-bottom: 2px solid #007bff;
}
</style>๐ Basic Configuration โ
Default Active Classes โ
Vue Router provides default active classes that work out of the box:
<template>
<!-- Uses default classes: router-link-active and router-link-exact-active -->
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</template>
<style>
.router-link-active {
color: #007bff;
}
.router-link-exact-active {
font-weight: bold;
border-left: 3px solid #007bff;
}
</style>Custom Active Classes โ
Override the default classes with your own:
<template>
<RouterLink
to="/products"
active-class="nav-active"
exact-active-class="nav-exact-active"
>
Products
</RouterLink>
</template>
<style>
.nav-active {
background-color: #f8f9fa;
color: #495057;
}
.nav-exact-active {
background-color: #007bff;
color: white;
border-radius: 4px;
}
</style>๐ง Advanced Techniques โ
Example 1: Complex Navigation Menu โ
<template>
<nav class="main-nav">
<RouterLink
v-for="item in navItems"
:key="item.to"
:to="item.to"
custom
v-slot="{ href, navigate, isActive, isExactActive }"
>
<a
:href="href"
@click="navigate"
:class="[
'nav-item',
{
'nav-item--active': isActive,
'nav-item--exact-active': isExactActive,
'nav-item--has-notification': item.hasNotification
}
]"
>
<span class="nav-icon">{{ item.icon }}</span>
<span class="nav-label">{{ item.label }}</span>
<span v-if="item.badge" class="nav-badge">{{ item.badge }}</span>
</a>
</RouterLink>
</nav>
</template>
<script setup>
import { ref } from 'vue'
const navItems = ref([
{
to: '/',
label: 'Home',
icon: '๐ ',
exact: true
},
{
to: '/dashboard',
label: 'Dashboard',
icon: '๐',
badge: '3'
},
{
to: '/messages',
label: 'Messages',
icon: '๐ฌ',
hasNotification: true,
badge: '12'
},
{
to: '/settings',
label: 'Settings',
icon: 'โ๏ธ'
}
])
</script>
<style scoped>
.main-nav {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 1rem;
}
.nav-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
text-decoration: none;
color: #6c757d;
border-radius: 8px;
transition: all 0.3s ease;
position: relative;
}
.nav-item:hover {
background-color: #f8f9fa;
color: #495057;
}
.nav-item--active {
background-color: #e3f2fd;
color: #007bff;
}
.nav-item--exact-active {
background-color: #007bff;
color: white;
font-weight: 600;
}
.nav-item--has-notification::before {
content: '';
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 8px;
height: 8px;
background-color: #dc3545;
border-radius: 50%;
}
.nav-icon {
font-size: 1.25rem;
}
.nav-label {
flex: 1;
}
.nav-badge {
background-color: #6c757d;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
}
.nav-item--exact-active .nav-badge {
background-color: rgba(255, 255, 255, 0.2);
}
</style>Example 2: Breadcrumb Navigation with Active States โ
<template>
<nav class="breadcrumb" aria-label="Breadcrumb">
<ol>
<li v-for="(crumb, index) in breadcrumbs" :key="index">
<RouterLink
:to="crumb.to"
:class="[
'breadcrumb-item',
{
'breadcrumb-item--active': crumb.isActive,
'breadcrumb-item--exact-active': crumb.isExactActive
}
]"
>
{{ crumb.label }}
</RouterLink>
<span
v-if="index < breadcrumbs.length - 1"
class="breadcrumb-separator"
>
/
</span>
</li>
</ol>
</nav>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
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
const crumbRoute = router.resolve(accumulatedPath)
const isActive = route.path.startsWith(accumulatedPath)
const isExactActive = route.path === accumulatedPath
crumbs.push({
label: path.charAt(0).toUpperCase() + path.slice(1),
to: isLast ? null : accumulatedPath,
isActive,
isExactActive
})
})
// Add home as first breadcrumb
crumbs.unshift({
label: 'Home',
to: '/',
isActive: route.path === '/',
isExactActive: route.path === '/'
})
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;
padding: 0.25rem 0.5rem;
border-radius: 4px;
transition: all 0.3s ease;
}
.breadcrumb-item:not(.breadcrumb-item--active) {
color: #007bff;
}
.breadcrumb-item:hover:not(.breadcrumb-item--active) {
background-color: #f8f9fa;
}
.breadcrumb-item--active {
color: #6c757d;
cursor: default;
}
.breadcrumb-item--exact-active {
background-color: #007bff;
color: white;
font-weight: 600;
}
.breadcrumb-separator {
color: #6c757d;
}
</style>๐ ๏ธ Practical Examples โ
Example 3: Tabbed Interface with Route Integration โ
<template>
<div class="tabbed-interface">
<nav class="tab-nav">
<RouterLink
v-for="tab in tabs"
:key="tab.id"
:to="tab.route"
custom
v-slot="{ href, navigate, isActive, isExactActive }"
>
<button
:href="href"
@click="navigate"
:class="[
'tab-button',
{
'tab-button--active': isActive,
'tab-button--exact-active': isExactActive
}
]"
>
{{ tab.label }}
<span v-if="tab.badge" class="tab-badge">{{ tab.badge }}</span>
</button>
</RouterLink>
</nav>
<div class="tab-content">
<RouterView />
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const tabs = ref([
{
id: 'overview',
label: 'Overview',
route: '/user/123/overview',
badge: null
},
{
id: 'activity',
label: 'Activity',
route: '/user/123/activity',
badge: '5'
},
{
id: 'settings',
label: 'Settings',
route: '/user/123/settings',
badge: null
},
{
id: 'billing',
label: 'Billing',
route: '/user/123/billing',
badge: 'New'
}
])
</script>
<style scoped>
.tabbed-interface {
border: 1px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
}
.tab-nav {
display: flex;
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.tab-button {
flex: 1;
padding: 1rem 1.5rem;
border: none;
background: transparent;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.tab-button:hover {
background-color: #e9ecef;
}
.tab-button--active {
background-color: white;
font-weight: 600;
}
.tab-button--exact-active {
background-color: #007bff;
color: white;
}
.tab-button--exact-active:hover {
background-color: #0056b3;
}
.tab-badge {
background-color: #6c757d;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
}
.tab-button--exact-active .tab-badge {
background-color: rgba(255, 255, 255, 0.3);
}
.tab-content {
padding: 2rem;
min-height: 300px;
}
</style>๐ Active State Configuration โ
Global Configuration โ
Configure default active classes for the entire application:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [...],
linkActiveClass: 'nav-active',
linkExactActiveClass: 'nav-exact-active'
})
export default routerExact Matching vs. Inclusive Matching โ
Understand the difference between exact and inclusive matching:
<template>
<!-- /user/123/profile will activate both links -->
<RouterLink to="/user" active-class="active">User (inclusive)</RouterLink>
<RouterLink to="/user" exact-active-class="exact-active">User (exact)</RouterLink>
<!-- Only /user will activate the exact link -->
</template>๐ Best Practices โ
โ Do โ
- Use Semantic Colors: Choose colors that convey meaning
- Provide Sufficient Contrast: Ensure accessibility compliance
- Use Multiple Visual Cues: Combine color, weight, and borders
- Test with Real Content: Ensure active states work with your actual routes
- Consider Mobile: Test active states on touch devices
โ Don't โ
- Rely Solely on Color: Support color-blind users
- Overanimate: Keep transitions subtle and professional
- Ignore Focus States: Maintain keyboard accessibility
- Forget Loading States: Consider what happens during navigation
๐ Related Features โ
- RouterLink Component: The foundation of active link functionality
- Navigation Guards: Control when navigation occurs
- Route Meta Fields: Store additional active state information
- CSS Transitions: Smooth active state animations
๐ก Pro Tip
Combine active link styling with Vue's `transition` component to create smooth, animated state changes that enhance the user experience without being distracting!
Ready to guide your users? Start implementing intelligent active links to create clear, professional navigation experiences! ๐