npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@todovue/tv-sidebar

v2.2.2

Published

A versatile sidebar component for TODOvue, offering customizable layouts, themes, and responsive design options.

Readme

TODOvue Sidebar (TvSidebar)

A versatile and flexible Vue 3 sidebar component with multiple display modes: lists, categories (labels), and images. Perfect for blogs, documentation sites, and web applications requiring sidebar navigation or content display. Compatible with both SPA and SSR environments (e.g. Nuxt 3).

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

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

Table of Contents

Features

  • Four display modes: List, Categories (labels), Image, and Grouped/Categorized
  • Hierarchical grouping: Organize content with collapsible sections and item counters
  • Event-driven interactions: No built-in navigation; emits click events with full objects
  • Item limit: Control how many items to display with the limit prop
  • Search/Filter: Real-time filtering across all display modes including grouped content
  • Optional clickable images: Enable with clickable to emit click events for images
  • Label/Category support: Display colored category labels with click events
  • Responsive design: Adapts to different screen sizes
  • SSR compatible: Works seamlessly in Nuxt 3 and SSR contexts
  • Customizable styling: Built with SCSS for easy theming
  • Tree-shakeable: Vue marked as external dependency

Installation

Using npm:

npm install @todovue/tv-sidebar

Using yarn:

yarn add @todovue/tv-sidebar

Using pnpm:

pnpm add @todovue/tv-sidebar

Note: This component depends on @todovue/tv-label for the categories mode.

Quick Start (SPA)

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

import { createApp } from 'vue'
import App from './App.vue'
import TvSidebar from '@todovue/tv-sidebar'
import '@todovue/tv-sidebar/style.css' // import styles
import '@todovue/tv-label/style.css' // import styles

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

Local import inside a component:

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'
import '@todovue/tv-sidebar/style.css' // import styles
import '@todovue/tv-label/style.css' // import styles

const sidebarData = {
  title: "Most Popular Blogs",
  list: [
    {
      id: 1,
      title: "10 Tips for Creating a Successful YouTube Channel",
      link: "/blog/youtube-tips",
    },
    {
      id: 2,
      title: "The Benefits of Meditation",
      link: "/blog/meditation",
    }
  ]
}
</script>

<template>
  <TvSidebar :data="sidebarData" />
</template>

Nuxt 4 / SSR Usage

Add the stylesheet to your nuxt.config.ts:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@todovue/tv-card/nuxt'
  ]
})

Create a plugin file: plugins/tv-sidebar.client.ts (or without .client suffix for SSR):

import { defineNuxtPlugin } from '#app'
import TvSidebar from '@todovue/tv-sidebar'

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

Use anywhere (no router dependency required):

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'
</script>

Component Registration Options

| Approach | When to use | |---------------------------------------------------------------------|------------------------------------------------| | Global via app.use(TvSidebar) | Many usages across app / design system install | | Local named import { TvSidebar } | Isolated / code-split contexts | | Direct default import import TvSidebar from '@todovue/tv-sidebar' | Single usage or manual registration |

Props

| Prop | Type | Default | Description | |-------------------|---------|---------------|------------------------------------------------------------------------------------------------------------------------------------------| | data | Object | {} | Main data object containing title and content (list, labels, or image). | | isImage | Boolean | false | Enables image display mode. | | isLabel | Boolean | false | Enables categories/labels display mode. | | isOutline | Boolean | false | Apply outline style to labels (only works with isLabel). | | size | String | 'md' | Sets size of labels (sm, md, lg). Only works with isLabel. | | limit | Number | 0 | Maximum number of items to display (0 = show all). | | clickable | Boolean | false | When true and isImage, the image becomes interactive and emits a click event with the image object. When false, image is static. | | searchable | Boolean | false | Enables search/filter input for filtering items in real-time across all display modes. | | searchPlaceholder | String | 'Search...' | Placeholder text for the search input field. | | grouped | Boolean | false | Enables grouped/categorized mode with collapsible sections. Requires data.groups array instead of data.list or data.labels. | | newLabelText | String | 'New' | Text to display in the "New" badge when isNew is true on an item. | | newLabelColor | String | '#FF3B30' | Hex color code for the "New" badge background. |

Events

| Event name (kebab) | Emits (camel) | Description | |--------------------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| | click | click | Emitted when a list item is clicked, when a label is clicked, and when an image is clicked (if clickable). The payload is always the full object. | | search | search | Emitted when the search query changes. The payload is the search string. |

Usage:

<TvSidebar 
  isLabel 
  searchable
  :data="categoriesData" 
  @click="handleAnyClick"
  @search="handleSearch"
/>

Usage Examples

Default List Mode

Display a numbered list of items that emit the full object on click:

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'

const listData = {
  title: "Most Popular Blogs",
  list: [
    {id: 1, title: "10 Tips for Creating a Successful YouTube Channel", link: "/blog/youtube-tips"},
    {id: 2, title: "The Benefits of Meditation and How to Get Started", link: "/blog/meditation"},
    {id: 3, title: "The Top 5 Destinations for Adventure Travel", link: "/blog/adventure-travel"}
  ],
}
function handleItemClick(item) {
  console.log('Item clicked:', item)
}
</script>

<template>
  <TvSidebar :data="listData" @click="handleItemClick" />
</template>

Categories (Labels) Mode

Display colored category labels:

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'

const categoriesData = {
  title: "Categories",
  labels: [
    {
      id: 1,
      name: "Vue.js",
      color: "#4FC08D",
    },
    {
      id: 2,
      name: "JavaScript",
      color: "#F0DB4F",
    },
    {
      id: 3,
      name: "HTML",
      color: "#E34F26",
    },
    {
      id: 4,
      name: "CSS",
      color: "#1572B6",
    }
  ]
}

function handleCategoryClick(category) {
  console.log('Category clicked:', category)
  // Navigate or filter by category
}
</script>

<template>
  <TvSidebar 
    isLabel 
    :data="categoriesData" 
    @clickLabel="handleCategoryClick"
  />
</template>

Image Mode

Display an image with title. Make it interactive with clickable if desired:

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'

const imageData = {
  title: "TODOvue Blog",
  image: {
    src: "https://todovue.com/vue.webp",
    alt: "TODOvue Logo",
    link: "https://todovue.com/"
  },
}
function handleImageClick(image) {
  console.log('Image clicked:', image)
}
</script>

<template>
  <!-- Non-clickable image -->
  <TvSidebar isImage :data="imageData" />
  
  <!-- Clickable image that emits the image object -->
  <TvSidebar isImage clickable :data="imageData" @click="handleImageClick" />
</template>

With Limit

Limit the number of displayed items:

<template>
  <!-- Show only first 5 items -->
  <TvSidebar :data="listData" :limit="5" />
  
  <!-- Show only first 8 categories -->
  <TvSidebar isLabel :data="categoriesData" :limit="8" />
</template>

With Search/Filter

Enable real-time search and filtering across all display modes:

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'

const listData = {
  title: 'Blog Posts',
  list: [
    { id: 1, title: 'Getting Started with Vue 3', link: '/blog/vue-3' },
    { id: 2, title: 'Understanding Composition API', link: '/blog/composition-api' },
    { id: 3, title: 'Building Reactive Forms', link: '/blog/reactive-forms' }
  ]
}

const categoriesData = {
  title: 'Categories',
  labels: [
    { id: 1, name: 'Vue.js', color: '#4FC08D' },
    { id: 2, name: 'JavaScript', color: '#F0DB4F' },
    { id: 3, name: 'TypeScript', color: '#007ACC' }
  ]
}

function handleSearch(query) {
  console.log('Search query:', query)
  // Optionally perform additional actions
}
</script>

<template>
  <!-- Searchable list with default placeholder -->
  <TvSidebar 
    :data="listData" 
    searchable 
    @search="handleSearch"
  />
  
  <!-- Searchable labels with custom placeholder -->
  <TvSidebar 
    isLabel 
    :data="categoriesData" 
    searchable 
    search-placeholder="Filter categories..."
    @search="handleSearch"
  />
  
  <!-- Searchable with limit (filtering applied before limiting) -->
  <TvSidebar 
    :data="listData" 
    searchable 
    :limit="5"
  />
</template>

Grouped/Categorized Mode

Organize content hierarchically with collapsible groups and item counters:

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'

const groupedData = {
  title: 'Blog Posts',
  groups: [
    {
      id: 1,
      name: 'Technical',
      collapsed: false,
      items: [
        { id: 1, title: '10 Tips for Creating a Successful YouTube Channel', link: '/blog/youtube' },
        { id: 2, title: 'How to Create High-Quality Visual Content', link: '/blog/visual-content' },
        { id: 3, title: 'The Power of Email Marketing', link: '/blog/email-marketing' }
      ]
    },
    {
      id: 2,
      name: 'Lifestyle',
      collapsed: true,
      items: [
        { id: 4, title: 'Why You Should Consider a Plant-Based Diet', link: '/blog/plant-based' },
        { id: 5, title: 'The Pros and Cons of Remote Work', link: '/blog/remote-work' }
      ]
    },
    {
      id: 3,
      name: 'Travel',
      collapsed: false,
      items: [
        { id: 6, title: 'The Top 5 Destinations for Adventure Travel', link: '/blog/adventure' }
      ]
    }
  ]
}

function handleItemClick(item) {
  console.log('Item clicked:', item)
}
</script>

<template>
  <!-- Basic grouped mode -->
  <TvSidebar 
    grouped 
    :data="groupedData" 
    @click="handleItemClick"
  />
  
  <!-- Grouped mode with search -->
  <TvSidebar 
    grouped 
    searchable 
    search-placeholder="Search posts..."
    :data="groupedData" 
    @click="handleItemClick"
  />
  
  <!-- Grouped mode with limit (applies to items per group) -->
  <TvSidebar 
    grouped 
    :limit="2"
    :data="groupedData" 
    @click="handleItemClick"
  />
</template>

New Items Indicator

Mark items as new by adding isNew: true to the item object. Customize the label text with newLabelText.

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'

const listData = {
  title: "Updates",
  list: [
    { id: 1, title: "Stable Release", link: "/v1", isNew: true },
    { id: 2, title: "Beta Release", link: "/beta" }
  ]
}
</script>

<template>
  <TvSidebar :data="listData" new-label-text="UPDATED" />
</template>

### Nuxt Integration
```diff
- Using with Nuxt routing:
- <TvSidebar linkTag="nuxt-link" :data="blogPosts" />

Nuxt works without router integration in the component. Handle navigation in your click handlers (see Navigation Handling below).

Data Structure

List Mode Data

{
  title: string,
  list: Array<{
    id: number | string,
    title: string,
    link?: string, // optional; use in your click handler if you want to navigate
    isNew?: boolean // optional; displays a 'New' badge
  }>
}

Labels/Categories Mode Data

{
  title: string,
  labels: Array<{
    id: number | string,
    name: string,
    color: string // Hex color code
  }>
}

Image Mode Data

{
  title: string,
  image: {
    src: string,    // Image URL
    alt: string,    // Alt text for accessibility
    link?: string   // optional; use in your click handler for manual navigation
  }
}

Grouped Mode Data

{
  title: string,
  groups: Array<{
    id: number | string,
    name: string,           // Group/category name
    collapsed: boolean,     // Initial collapsed state
    items: Array<{
      id: number | string,
      title: string,
      link?: string,       // optional; use in your click handler if you want to navigate
      isNew?: boolean      // optional; displays a 'New' badge
    }>
  }>
}

Styling

The component uses SCSS for styling. Styles are automatically included when you import the component. The sidebar includes:

  • Clean, minimal design
  • Responsive layout
  • Title with separator line
  • Hover effects on interactive items
  • Proper spacing and typography

To customize styles, you can override the CSS classes:

.tv-sidebar-body {
  /* Container styles */
}

.tv-sidebar {
  /* Main sidebar styles */
}

.tv-sidebar-title h1 {
  /* Title styles */
}

.tv-sidebar-content-li {
  /* List item styles */
}

Navigation Handling

Since the component does not perform navigation, handle it in your click handlers. Example with Vue Router:

<script setup>
import { TvSidebar } from '@todovue/tv-sidebar'
import { useRouter } from 'vue-router'

const router = useRouter()

const listData = {
  title: 'Recent Posts',
  list: [
    { id: 1, title: 'Getting Started with Nuxt 3', link: '/blog/nuxt-3' },
    { id: 2, title: 'Vue Composition API', link: '/blog/composition-api' }
  ]
}

function handleClick(item) {
  if (item?.link) router.push(item.link)
}
</script>

<template>
  <TvSidebar :data="listData" @click="handleClick" />
</template>

Accessibility

  • Semantic structure with clear headings
  • Alt text support for images
  • Interactive items emit click events; if you need keyboard accessibility, consider handling keydown (Enter/Space) on your side or wrapping with accessible elements/roles
  • Color contrast considerations for labels

SSR Notes

  • No direct DOM access (window / document) → safe for SSR
  • Compatible with Nuxt 3 out of the box
  • Styles are bundled and auto-imported
  • No router/nuxt-link dependency inside the component

Development

git clone https://github.com/TODOvue/tv-sidebar.git
cd tv-sidebar
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.

License

MIT © TODOvue

Dependencies

  • vue (^3.0.0) - Peer dependency
  • @todovue/tv-label - Used for category/label display mode

Attributions

Crafted with ❤️ for the TODOvue component ecosystem by Cristhian Daza