A flexible, responsive Vue 3 menu component with integrated search functionality, mobile-friendly navigation, and easy customization. Perfect for Single Page Apps or Server-Side Rendered (SSR) environments like Nuxt 3.
- Features
- Installation
- Quick Start (SPA)
- Nuxt 3 / SSR Usage
- Component Registration Options
- Props
- Events
- Usage Examples
- Responsive Behavior
- Customization (Styles / Theming)
- Accessibility
- SSR Notes
- Development
- Contributing
- License
- Responsive navigation menu with desktop and mobile views
- Integrated search functionality powered by
@todovue/tv-search - Logo/image click support with custom event handling
- Menu item click events with data payload
- Automatic mobile menu toggle with hamburger icon
- Clean, modern design with customizable styling
- Works seamlessly in SPA and SSR (Nuxt 3) contexts
- Tree-shake friendly (Vue marked external in library build)
Using npm:
npm install @todovue/tv-menuUsing yarn:
yarn add @todovue/tv-menuUsing pnpm:
pnpm add @todovue/tv-menuGlobal registration (main.js / main.ts):
import { createApp } from 'vue'
import App from './App.vue'
import TvMenu from '@todovue/tv-menu'
createApp(App)
.use(TvMenu) // enables <TvMenu /> globally
.mount('#app')Local import inside a component:
<script setup>
import { TvMenu } from '@todovue/tv-menu'
const menuItems = [
{ id: 1, title: 'Home', url: '/' },
{ id: 2, title: 'About', url: '/about' },
{ id: 3, title: 'Contact', url: '/contact' }
]
function handleMenuClick(menu) {
console.log('Clicked:', menu)
// Navigate to menu.url or perform custom action
}
function handleImageClick() {
console.log('Logo clicked')
}
function handleSearch(searchTerm) {
console.log('Search:', searchTerm)
}
</script>
<template>
<TvMenu
:menus="menuItems"
placeholder="Search..."
titleButton="Search"
imageMenu="https://example.com/logo.png"
@clickImage="handleImageClick"
@clickMenu="handleMenuClick"
@searchMenu="handleSearch"
/>
</template>Create a plugin file: plugins/tv-menu.client.ts:
import { defineNuxtPlugin } from '#app'
import TvMenu from '@todovue/tv-menu'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(TvMenu)
})Use anywhere in your Nuxt app:
<template>
<TvMenu
:menus="navigationItems"
placeholder="Search..."
imageMenu="/logo.png"
@clickMenu="navigateTo"
/>
</template>Optional direct import (no plugin):
<script setup>
import { TvMenu } from '@todovue/tv-menu'
</script>| Approach | When to use |
|---|---|
Global via app.use(TvMenu) |
Many usages across app / design system install |
Local named import { TvMenu } |
Isolated / code-split contexts |
Direct default import import TvMenu from '@todovue/tv-menu' |
Single usage or manual registration |
| Prop | Type | Default | Description |
|---|---|---|---|
| menus | Array | [] | Array of menu items with { id, title, url } structure. |
| placeholder | String | '' | Placeholder text for the search input. |
| titleButton | String | '' | Label for the search button. |
| imageMenu | String | '' | URL of the logo/image to display in the menu header. |
Each item in the menus array should have this structure:
{
id: Number, // unique identifier
title: String, // display text
url: String // navigation path or identifier
}| Event name (kebab) | Payload | Description |
|---|---|---|
clickImage |
— | Emitted when the logo/image is clicked. |
clickMenu |
menu object | Emitted when a menu item is clicked. |
searchMenu |
search term | Emitted when search is performed (string value). |
Usage examples:
<TvMenu
@clickImage="handleLogoClick"
@clickMenu="handleNavigation"
@searchMenu="performSearch"
/><script setup>
import { TvMenu } from '@todovue/tv-menu'
import { useRouter } from 'vue-router'
const router = useRouter()
const menus = [
{ id: 1, title: 'Home', url: '/' },
{ id: 2, title: 'About', url: '/about' },
{ id: 3, title: 'Blog', url: '/blog' },
{ id: 4, title: 'Contact', url: '/contact' }
]
function navigateToPage(menu) {
router.push(menu.url)
}
</script>
<template>
<TvMenu
:menus="menus"
placeholder="Search pages..."
titleButton="Go"
imageMenu="/logo.png"
@clickMenu="navigateToPage"
/>
</template><script setup>
import { TvMenu } from '@todovue/tv-menu'
import { ref } from 'vue'
const searchResults = ref([])
function handleSearch(term) {
// Perform search logic
fetch(`/api/search?q=${term}`)
.then(res => res.json())
.then(data => searchResults.value = data)
}
</script>
<template>
<TvMenu
:menus="navigationItems"
placeholder="Search our site..."
titleButton="Search"
imageMenu="/brand-logo.png"
@searchMenu="handleSearch"
/>
</template><script setup>
import { TvMenu } from '@todovue/tv-menu'
const categories = [
{ id: 1, title: 'Shop', url: '/shop' },
{ id: 2, title: 'New Arrivals', url: '/new' },
{ id: 3, title: 'Sale', url: '/sale' },
{ id: 4, title: 'My Account', url: '/account' }
]
function onLogoClick() {
window.location.href = '/'
}
function navigateTo(menu) {
window.location.href = menu.url
}
function searchProducts(query) {
// Product search logic
console.log('Searching products:', query)
}
</script>
<template>
<TvMenu
:menus="categories"
placeholder="Search products..."
titleButton="Find"
imageMenu="https://example.com/store-logo.png"
@clickImage="onLogoClick"
@clickMenu="navigateTo"
@searchMenu="searchProducts"
/>
</template>- Desktop view: Full horizontal menu with all items visible + integrated search
- Mobile view: Hamburger icon that toggles a slide-in menu overlay
- Automatic breakpoint: Menu adapts automatically using CSS media queries
- Touch-friendly: All interactive elements are optimized for touch devices
The component uses SCSS with a modular structure. You can override styles by targeting the following CSS classes:
.tv-menu-container- Main menu wrapper.tv-menu-image- Logo container.tv-menu-items- Desktop menu items container.tv-menu-item- Individual menu item (desktop).tv-menu-icon- Hamburger menu icon.tv-menu-items-mobile- Mobile menu overlay.tv-menu-item-mobile- Individual menu item (mobile)
/* Custom menu colors */
.tv-menu-container {
background-color: #0f2e5b;
}
.tv-menu-item {
color: #ffffff;
font-weight: 600;
}
.tv-menu-item:hover {
color: #ff4081;
}
/* Custom logo size */
.tv-menu-image img {
width: 200px;
height: auto;
}- Semantic HTML5 elements (
<header>,<nav>,<ul>,<li>) - Keyboard navigation support for all interactive elements
- Alt text support for logo image
- Click events work with keyboard (Enter/Space)
- Mobile menu properly handles focus management
- Always provide meaningful
alttext via the logo image URL - Ensure menu items have descriptive titles
- Test keyboard navigation (Tab, Enter, Escape)
- No direct DOM (
window/document) access in core component → safe for SSR - Styles are automatically injected when you import the library
- Works with Vite-based SSR and Nuxt 3 out of the box
- Mobile menu state is managed via Vue reactivity (no localStorage dependencies)
git clone https://github.com/TODOvue/tv-menu.git
cd tv-menu
npm install
npm run dev # run demo playground
npm run build # build libraryLocal demo served from Vite using index.html + src/demo examples.
PRs and issues welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
Please ensure:
- Code follows existing style conventions
- Tests pass (when available)
- Documentation is updated for new features
MIT © TODOvue
Crafted for the TODOvue component ecosystem
