TODOvue Menu (TvMenu)

January 20, 2026 · View on GitHub

TODOvue logo

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.

npm npm downloads npm total downloads License Release Date Bundle Size Node Version Last Commit Stars

Demo: https://ui.todovue.blog/menu

Table of Contents

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-menu

Using yarn:

yarn add @todovue/tv-menu

Using pnpm:

pnpm add @todovue/tv-menu

Quick 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 4 / 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

ApproachWhen 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

PropTypeDefaultDescription
menusArray[]Array of menu items with { id, title, url } structure.
placeholderString''Placeholder text for the search input.
titleButtonString''Label for the search button.
imageMenuString''URL of the logo/image to display in the menu header.
noResultsTextString'No results found'Text displayed when search yields no results.
activeMenuString/NumbernullThe ID of the currently active menu item.

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)PayloadDescription
clickImageEmitted when the logo/image is clicked.
clickMenumenu objectEmitted when a menu item is clicked.
searchMenusearch termEmitted when search is performed (string value).
update:activeMenumenu idEmitted when the active menu item changes.

Usage examples:

<TvMenu
  @clickImage="handleLogoClick"
  @clickMenu="handleNavigation"
  @searchMenu="performSearch"
/>

Slots

Slot NamePropsDescription
logoReplace the default logo image.
item{ menu, isActive }Customize the rendering of each menu item.
action-endAdd content after the search bar (e.g., User Profile).

Example with Slots

<TvMenu :menus="menus" v-model:activeMenu="activeId">
  <template #logo>
    <div class="my-logo">My App</div>
  </template>
  
  <template #item="{ menu, isActive }">
    <span :class="{ 'highlight': isActive }">
      <i class="icon" v-if="menu.icon" :class="menu.icon"></i>
      {{ menu.title }}
    </span>
  </template>

  <template #action-end>
     <div class="user-profile">User</div>
  </template>
</TvMenu>

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 alt text 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 library

Local 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