@todovue/tv-menu
v1.1.2
Published
A Vue menu component with modern design and easy customization.
Maintainers
Readme
TODOvue Menu (TvMenu)
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.
Demo: https://ui.todovue.blog/menu
Table of Contents
- 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
Features
- 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)
Installation
Using npm:
npm install @todovue/tv-menuUsing yarn:
yarn add @todovue/tv-menuUsing pnpm:
pnpm add @todovue/tv-menuQuick Start (SPA)
Global registration (main.js / main.ts):
import { createApp } from 'vue'
import App from './App.vue'
import TvMenu from '@todovue/tv-menu'
import '@todovue/tv-menu/style.css' // import styles
import '@todovue/tv-search/style.css' // import styles
import '@todovue/tv-button/style.css' // import styles
createApp(App)
.use(TvMenu) // enables <TvMenu /> globally
.mount('#app')Local import inside a component:
<script setup>
import { TvMenu } from '@todovue/tv-menu'
import '@todovue/tv-menu/style.css' // import styles
import '@todovue/tv-search/style.css' // import styles
import '@todovue/tv-button/style.css' // import styles
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>Nuxt 3 / SSR Usage
Create a plugin file: plugins/tv-menu.client.ts:
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@todovue/tv-menu/nuxt'
]
})Use anywhere in your Nuxt app:
<template>
<TvMenu
:menus="navigationItems"
placeholder="Search..."
imageMenu="/logo.png"
@clickMenu="navigateTo"
/>
</template>Component Registration Options
| 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 |
Props
| 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. |
Menu Item Structure
Each item in the menus array should have this structure:
{
id: Number, // unique identifier
title: String, // display text
url: String // navigation path or identifier
}Events
| 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"
/>Usage Examples
Basic Navigation Menu
<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>With Search Functionality
<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>E-commerce Navigation
<script setup>
import '@todovue/tv-menu/style.css'
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>Responsive Behavior
- 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
Customization (Styles / Theming)
The component uses SCSS with a modular structure. You can override styles by targeting the following CSS classes:
Main 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)
Example Custom Styling
/* 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;
}Accessibility
- 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
Best Practices
- Always provide meaningful
alttext via the logo image URL - Ensure menu items have descriptive titles
- Test keyboard navigation (Tab, Enter, Escape)
SSR Notes
- 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)
Development
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.
Contributing
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
License
MIT © TODOvue
Attributions
Crafted for the TODOvue component ecosystem
