Skip to content
/ tv-toc Public

TvToc is a simple table of contents component for Vue 3 that automatically generates a TOC based on the headings in your content.

License

Notifications You must be signed in to change notification settings

TODOvue/tv-toc

TODOvue logo

TODOvue TOC Component (TvToc)

A lightweight Vue 3 component to render a Table of Contents (TOC) for your articles or documentation, with smooth scrolling and nested sections support.

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

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


Table of Contents


Features

  • Simple and focused Table of Contents (TOC) component for Vue 3.
  • Supports nested sections via children links.
  • Smooth scrolling to headings using scrollIntoView.
  • URL hash is updated using history.pushState for better navigation and shareable links.
  • Works in SPA (Vite, Vue CLI) and Nuxt 3 (with client-side rendering constraints).
  • Ships with minimal, customizable styles.

Installation

Using npm:

npm install @todovue/tv-toc

Using yarn:

yarn add @todovue/tv-toc

Using pnpm:

pnpm add @todovue/tv-toc

Usage of Styles

Vue/Vite (SPA)

Import the CSS generated by the library in your main entry file:

// main.ts
import { createApp } from 'vue'
import App from './App.vue'

import '@todovue/tv-toc/style.css'
import { TvToc } from '@todovue/tv-toc'

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

Nuxt 3/4

Add the library's CSS to your Nuxt configuration:

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

Quick Start (SPA)

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

import { createApp } from 'vue'
import App from './App.vue'
import TvToc from '@todovue/tv-toc'

createApp(App)
  .component('TvToc', TvToc)
  .mount('#app')

Local import inside a component:

<script setup>
import { TvToc } from '@todovue/tv-toc'

const toc = {
  title: 'On this page',
  links: [
    { id: 'introduction', text: 'Introduction' },
    {
      id: 'getting-started',
      text: 'Getting started',
      children: [
        { id: 'installation', text: 'Installation' },
        { id: 'basic-usage', text: 'Basic usage' },
      ],
    },
    { id: 'api', text: 'API Reference' },
  ],
}
</script>

<template>
  <div class="page-layout">
    <main class="page-content">
      <h2 id="introduction">Introduction</h2>
      <!-- ... -->
      <h2 id="getting-started">Getting started</h2>
      <h3 id="installation">Installation</h3>
      <h3 id="basic-usage">Basic usage</h3>
      <!-- ... -->
      <h2 id="api">API Reference</h2>
    </main>

    <aside class="page-toc">
      <TvToc :toc="toc" />
    </aside>
  </div>
</template>

Nuxt 3 / SSR Usage

Create a plugin file: plugins/tv-toc.client.ts (client-only because it uses document and history under the hood when scrolling):

import { defineNuxtPlugin } from '#app'
import TvToc from '@todovue/tv-toc'

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

Use anywhere (recommended inside <client-only> when using Nuxt 3):

<template>
  <client-only>
    <TvToc :toc="toc" />
  </client-only>
</template>

Direct import (no plugin):

<script setup>
import { TvToc } from '@todovue/tv-toc'
</script>

<template>
  <TvToc :toc="toc" />
</template>

Component Registration Options

Approach When to use
Global via app.component('TvToc', TvToc) Frequent use across the app
Local named import { TvToc } Isolated/code-split contexts
Direct default import import TvToc from ... Single use or manual registration

Props

Name Type Default Description Required
toc Object - TOC configuration: title and list of links (with optional nested children) true

toc shape

type TocLink = {
  id: string
  text: string
  children?: TocLink[]
}

type Toc = {
  title?: string
  links: TocLink[]
}
  • title: Optional title shown at the top of the TOC (h3).
  • links: Array of top-level sections.
    • id: Must match the id attribute of the target heading in your content.
    • text: Label shown in the TOC.
    • children: Optional array of sub-sections, rendered as nested list.

Composable: useToc

This composable is used internally by TvToc but can also be imported directly if needed.

import { useToc } from '@todovue/tv-toc'

const { formatId, scrollToId } = useToc()

API

Name Type Description
formatId (id: string) => string Returns a hash-based id string (e.g. "#section-id").
scrollToId (id: string) => void Smooth scrolls to the element with that id and updates hash.

Note: scrollToId accesses document and history, so it should run only in the browser (e.g. in event handlers or inside onMounted).


Customization (Styles)

The component ships with minimal default styles, exposed through the built CSS file and scoped CSS classes.

Main CSS classes:

  • .tv-toc — Root <nav> container.
  • .tv-toc-title — Title of the TOC.
  • .tv-toc-list — Top-level list container.
  • .tv-toc-item — Top-level list item.
  • .tv-toc-link — Anchor for top-level items.
  • .tv-toc-sublist — Nested list container for children.
  • .tv-toc-subitem — Nested list item.
  • .tv-toc-sublink — Anchor for nested items.

You can override these styles in your own global stylesheet:

/* example overrides */
.tv-toc {
  font-size: 0.9rem;
}

.tv-toc-link,
.tv-toc-sublink {
  color: #4b5563;
}

.tv-toc-link:hover,
.tv-toc-sublink:hover {
  color: #111827;
}

If you are using SCSS, you can also rely on your own design tokens and overrides. The package itself internally uses SCSS (see src/assets/scss/_variables.scss and src/assets/scss/style.scss).


SSR Notes

  • The component can be rendered on the server (template is static), but scrolling behavior uses browser APIs.
  • scrollToId uses document.getElementById and history.pushState; these are only invoked in event handlers on the client.
  • When using Nuxt 3, prefer registering TvToc in a *.client.ts plugin or wrap usages in <client-only> to avoid hydration edge cases in environments with stricter SSR.

Examples

This repository includes a small demo application built with Vite.

  • Basic TOC example.
  • Blog-like layout with nested headings.

To run the demo locally, see the Development section.


Development

git clone https://github.com/TODOvue/tv-toc.git
cd tv-toc
npm install
npm run dev     # run local demo
npm run build   # build library

The local demo is served with Vite using index.html and examples in src/demo.

To build the standalone demo used for documentation:

npm run build:demo

Contributing

PRs and issues are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.


License

MIT © TODOvue


Attributions

Crafted for the TODOvue component ecosystem

About

TvToc is a simple table of contents component for Vue 3 that automatically generates a TOC based on the headings in your content.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors 2

  •  
  •