Skip to content

CSS classes and IDs ​

Public selectors are the theming API — they are stable across minor releases. See Styling for the full tutorial and copy-paste recipes. Two sources define them: src/runtime/components/TableOfContents.vue:759 (defaults) and src/runtime/components/TocTree.vue:5.

Complete selector map ​

Outer wrapper ​

Every render state uses a div.nuxt-toc. Additional state classes are appended:

SelectorMeaningRender condition
.nuxt-tocRoot wrapper (always present)always
.nuxt-toc--pendingLoading stateshouldAutoFetch && pending → “Loading table of contents…”
.nuxt-toc--emptyFinished with no resultdocumentMissing / emptyLinks / autoFetchFailed with message + <code>resolvedPath</code>
.nuxt-toc--errorFetch failed (plugin missing or threw)autoFetchFailed — subsets .nuxt-toc--empty
#toc-titleTitle <span role="heading" aria-level="2">present when links exist or isTitleShownWithNoContent
#toc-containerRoot <ul role="list">present when hasLinks

Recursive list (TocTree.vue) ​

SelectorElementWhen it appears
.toc-itemEvery div wrapping a linkalways per link
.toc-topitemTop-level itemsroot === true (level 1)
.toc-sublist-itemNested itemsroot === false
.toc-sublistNested <ul>root === false
.toc-topitem-and-sublist<li> at depth 1root === true (useful for group spacing)
.toc-linkEvery <a href="#id">always
.toc-toplinkAnchors at depth 1root === true
.toc-sublinkAnchors at depth > 1root === false
.active-toc-itemActive wrapper (observer)isActive(id) === true
.active-toc-topitemActive top-levelroot && isActive
.active-toc-sublist-itemActive nested!root && isActive
#toc-item-${id}Per-link wrapper idalways — unique per heading id

Combined active state for a top-level link looks like:

html
<div
  id="toc-item-installation"
  class="toc-item toc-topitem active-toc-item active-toc-topitem"
  role="heading"
  aria-level="2"
>
  <a href="#installation" class="toc-link toc-toplink" role="link">Installation</a>
</div>

Default minimal CSS (reproduced) ​

Scoped to .nuxt-toc so it does not affect other lists on the page:

css
.nuxt-toc {
  color: inherit;
}
.nuxt-toc--pending,
.nuxt-toc--empty {
  opacity: 0.75;
  font-size: 0.9em;
}
.nuxt-toc--empty code {
  font-size: 0.9em;
}
.nuxt-toc .active-toc-item {
  color: #fef08a;
}
.nuxt-toc .toc-sublist-item {
  padding-left: 1rem;
}
.nuxt-toc .toc-sublist .toc-sublist .toc-sublist-item {
  padding-left: 1.5rem;
}
.nuxt-toc a.toc-link {
  text-decoration: none;
  color: inherit;
}
.nuxt-toc ul,
.nuxt-toc ol {
  list-style: none;
  padding: 0;
  margin: 0;
}

Common overrides ​

css
/* Brand active highlight */
#toc-title {
  font-weight: 700;
  margin-bottom: 0.5rem;
}
.toc-link:hover {
  text-decoration: underline;
}
.active-toc-item {
  color: var(--brand, #38bdf8);
}

/* Left indicator */
.toc-item {
  border-left: 2px solid transparent;
}
.active-toc-sublist-item {
  border-left-color: var(--brand);
}

/* Hide nesting via prop instead of display:none where possible */

Prefer the prop (:depth="1") over hiding nested lists with display: none — the component avoids creating those DOM nodes at all and scroll-spy skips them. See Styling and Recipes — hide nested links.

Keep overrides global

The TOC tree is a child subtree. <style scoped> without :deep() will not reach nested TocTree levels. Put your overrides in app/assets/… or wrap with :deep(.toc-link) in a scoped block.

Released under the MIT License.