Skip to content

TODOvue/tv-search

TODOvue logo

TODOvue Search (TvSearch)

A fast, accessible, and fully customizable search interface component for Vue 3 applications. Provides an elegant modal search experience with keyboard shortcuts, real-time filtering, and complete style customization. Works seamlessly in Single Page Apps or Server-Side Rendered (SSR) environments (e.g. Nuxt 3).

npm Netlify Status npm downloads npm total downloads License Release Date

Demo: https://tv-search.netlify.app/


Table of Contents


Features

  • Keyboard-first UX: Open with Ctrl+K / Cmd+K, close with Esc
  • Real-time filtering: Search as you type with instant results
  • Modal interface: Clean overlay design that focuses user attention
  • Fully customizable: Override colors for body, input, button, and text
  • Accessible: Built with semantic HTML and keyboard navigation
  • Lightweight: Minimal dependencies, Vue 3 marked as peer dependency
  • SSR compatible: Works in Nuxt 3 and other SSR frameworks
  • Auto-focus: Input field receives focus automatically when opened
  • Click-away close: Modal closes when clicking outside the content area
  • Flexible results: Pass any array of searchable items with custom properties

Installation

Using npm:

npm install @todovue/tv-search

Using yarn:

yarn add @todovue/tv-search

Using pnpm:

pnpm add @todovue/tv-search

Quick Start (SPA)

Global registration (main.js / main.ts):

import { createApp } from 'vue'
import App from './App.vue'
import { TvSearch } from '@todovue/tv-search'

createApp(App)
  .use(TvSearch) // enables <TvSearch /> globally
  .mount('#app')

Alternatively, register as a component:

import { createApp } from 'vue'
import App from './App.vue'
import { TvSearch } from '@todovue/tv-search'

const app = createApp(App)
app.component('TvSearch', TvSearch)
app.mount('#app')

Local import inside a component:

<script setup>
import { ref } from 'vue'
import { TvSearch } from '@todovue/tv-search'

const results = ref([
  {
    id: 1,
    title: 'How to use Vue 3',
    description: 'Vue 3 is the latest version of Vue.js',
    url: 'https://todovue.com/blog/how-to-use-vue-3',
  },
  {
    id: 2,
    title: 'How to use Vite',
    description: 'Vite is a build tool for modern web development',
    url: 'https://todovue.com/blog/how-to-use-vite',
  },
  {
    id: 3,
    title: 'How to use Pinia',
    description: 'Pinia is a modern store for Vue 3',
    url: 'https://todovue.com/blog/how-to-use-pinia',
  },
])

function handleSearch(query) {
  console.log('Search query:', query)
  // Handle search logic here
}
</script>

<template>
  <tv-search
    placeholder="Search documentation..."
    titleButton="Search"
    :results="results"
    @search="handleSearch"
  />
</template>

Nuxt 3 / SSR Usage

Create a plugin file: plugins/tv-search.client.ts (client-only is recommended since it uses keyboard events):

import { defineNuxtPlugin } from '#app'
import { TvSearch } from '@todovue/tv-search'

export default defineNuxtPlugin(nuxtApp => {
  nuxtApp.vueApp.component('TvSearch', TvSearch)
})

Or use the plugin install method:

import { defineNuxtPlugin } from '#app'
import { TvSearch } from '@todovue/tv-search'

export default defineNuxtPlugin(nuxtApp => {
  nuxtApp.vueApp.use(TvSearch)
})

Then use anywhere in your Nuxt app:

<template>
  <tv-search
    placeholder="Search site..."
    titleButton="Search"
    :results="searchResults"
    @search="onSearch"
  />
</template>

<script setup>
const searchResults = ref([
  // your search results
])

function onSearch(query) {
  // handle search
}
</script>

Optional direct import (no plugin needed):

<script setup>
import { TvSearch } from '@todovue/tv-search'
</script>

Component Registration Options

Approach When to use
Global via app.use(TvSearch) Design system / used across many pages
Global via app.component('TvSearch', TvSearch) Custom component name / multiple search components
Local named import import TvSearch from '...' Single page usage / code splitting
Nuxt plugin .client.ts SSR apps with client-side interactions

Props

Prop Type Default Description Required
placeholder String "" Placeholder text for the search input field true
titleButton String "" Text displayed on the search button true
results Array [] Array of searchable items (see Results Data Structure) true
customStyles Object {} Custom color scheme for theming (see Customization) false

customStyles Object

Customize the appearance by passing a customStyles object with any of these properties:

Property Type Default Description
bgBody String "#0E131F" Background color of the modal overlay (with 0.9 opacity)
bgInput String "#B9C4DF" Background color of the search input area
bgButton String "#EF233C" Background color of the search button
colorButton String "#F4FAFF" Text color of the search button

Events

Event Payload Type Description
search String Emitted when search is triggered (Enter key or button click). Returns the trimmed search query.

Example:

<tv-search
  placeholder="Search..."
  titleButton="Go"
  :results="items"
  @search="handleSearch"
/>

<script setup>
function handleSearch(query) {
  console.log('User searched for:', query)
  // Perform API call, route navigation, etc.
}
</script>

Keyboard Shortcuts

Shortcut Action
Ctrl + K / Cmd + K Open the search modal
Escape Close the search modal
Enter Execute search with current input
Click outside modal Close the search modal

Customization (Styles / Theming)

You can override the default color scheme by passing a customStyles object:

<script setup>
import { ref } from 'vue'
import { TvSearch } from '@todovue/tv-search'

const customStyles = ref({
  bgBody: "#1e1d23",
  bgInput: "#8673a1",
  bgButton: "#80286e",
  colorButton: "#d7c9c9",
})

const results = ref([
  // your results
])
</script>

<template>
  <tv-search
    placeholder="Type to search..."
    titleButton="Search"
    :results="results"
    :customStyles="customStyles"
  />
</template>

Example Custom Themes

Dark Theme:

const darkTheme = {
  bgBody: "#0E131F",
  bgInput: "#1F2937",
  bgButton: "#3B82F6",
  colorButton: "#FFFFFF",
}

Light Theme:

const lightTheme = {
  bgBody: "#F9FAFB",
  bgInput: "#FFFFFF",
  bgButton: "#6366F1",
  colorButton: "#FFFFFF",
}

Brand Theme:

const brandTheme = {
  bgBody: "#0A4539",
  bgInput: "#284780",
  bgButton: "#80286E",
  colorButton: "#D5B7B7",
}

Results Data Structure

The results prop expects an array of objects with the following structure:

interface SearchResult {
  id: number | string;    // Unique identifier (required for :key)
  title: string;          // Displayed in search results (required)
  description?: string;   // Additional info (optional, not currently displayed)
  url?: string;           // Navigation target (optional, not currently used in component)
  [key: string]: any;     // Any additional custom properties
}

Example:

const results = [
  {
    id: 1,
    title: 'Getting Started with Vue 3',
    description: 'Learn the basics of Vue 3 composition API',
    url: '/docs/vue3-intro',
    category: 'Tutorial',
  },
  {
    id: 2,
    title: 'Understanding Reactivity',
    description: 'Deep dive into Vue reactivity system',
    url: '/docs/reactivity',
    category: 'Advanced',
  },
]

Note: The component currently filters results based on the title property matching the user input (case-insensitive). You can handle the @search event to implement custom search logic or navigation.


Accessibility

  • Keyboard navigation: Full support for Ctrl+K/Cmd+K to open, Esc to close, and Enter to search
  • Focus management: Input automatically receives focus when modal opens and is selected for immediate typing
  • Semantic HTML: Uses proper <button>, <input>, and modal structure
  • Click-away: Modal closes when clicking the overlay, providing intuitive UX

Recommendations:

  • Provide clear, descriptive placeholder text
  • Use meaningful titleButton text (e.g., "Search", "Find", "Go")
  • Ensure sufficient color contrast when using customStyles
  • Consider adding aria-label attributes for screen reader support in future versions

SSR Notes

  • Safe for SSR: No direct DOM access (window / document) during module initialization
  • Event listeners: Keyboard event listeners are registered in onMounted and cleaned up in onBeforeUnmount
  • Client-side only: Keyboard shortcuts require browser environment; use .client.ts plugin in Nuxt
  • Icons: SVG icons are loaded via Vite's import.meta.glob, which works in both SPA and SSR builds
  • No manual CSS import required: Styles are automatically injected via vite-plugin-css-injected-by-js

Roadmap

Feature Status
Support for result url navigation Planned
Display description in results Planned
Customizable search icon Planned
Multiple keyboard shortcut options Considering
Result categorization / grouping Considering
Highlight matching text in results Considering
Recent searches history Considering
Loading state indicator Considering
Pagination for large result sets Considering
Fuzzy search / advanced filtering Considering
Theming via CSS variables Considering
TypeScript type definitions improvement Planned
ARIA attributes enhancement Planned

Development

Clone the repository and install dependencies:

git clone https://github.com/TODOvue/tv-search.git
cd tv-search
yarn install

Run development server with demo playground:

yarn dev

Build the library:

yarn build

Build demo site:

yarn build:demo

The demo is served from Vite using index.html + src/demo examples.


Contributing

Contributions are welcome! Please read our Contributing Guidelines and Code of Conduct before submitting PRs.

How to contribute:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Changelog

See CHANGELOG.md for release history and version changes.


License

MIT © TODOvue


Attributions

Crafted for the TODOvue component ecosystem

About

TvSearch provides a fast, accessible, and fully customizable search interface for Vue 3 apps.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors 2

  •  
  •