Comments for VitePress and MkDocs documentation sites without a GitHub login
Documentation sites default to giscus or utterances, and both require every reader to authorise a GitHub app before they can ask a question. This guide adds a hosted comment section that lets readers comment as guests, with the exact theme extension for VitePress and the template override for MkDocs and Material for MkDocs.
Documentation sites have a default answer for comments, and it is GitHub. giscus stores comments in GitHub Discussions, utterances stores them in GitHub Issues, and both are excellent if every reader of your docs has a GitHub account and is willing to use it. giscus's own site is direct about the condition: "To comment, visitors must authorize the giscus app to post on their behalf using the GitHub OAuth flow" (giscus.app, read on 3 September 2026). utterances says the same thing in almost the same words (utteranc.es, read on 3 September 2026).
For a library whose users are all developers, that is a fair trade. For product documentation read by customers, support staff, students, or anyone who does not live on GitHub, it means the reader who found a mistake on your install page closes the tab instead of telling you. This guide adds a comment section that lets readers comment as guests or sign in with Google, GitHub, X or Facebook, on the two documentation generators where the GitHub default is strongest: VitePress and MkDocs.
The widget is EchoThread's, a single script under 60 KB gzipped that loads asynchronously after the page. Every comment is scored by a machine-learning spam filter before it appears, which matters more on docs than on blogs, because docs pages are long-lived and a spam comment on your install page is there for years. We build EchoThread, so treat our claims as claims.
What you need
- A VitePress site (1.x) or an MkDocs site, with or without the Material theme.
- An EchoThread account (create one free), a site added in the dashboard with the domain your docs are served from, and that site's API key.
VitePress
VitePress's default theme exposes named layout slots, and the one under the article body is doc-after. You extend the theme with a wrapper that fills that slot (Extending the default theme, read on 3 September 2026).
Step 1: the component
Create .vitepress/theme/Comments.vue:
<script setup>
import { onMounted, watch, nextTick } from 'vue'
import { useRoute, useData } from 'vitepress'
const route = useRoute()
const { page, frontmatter } = useData()
const WIDGET_SRC = 'https://cdn.echothread.io/widget.js'
function mount() {
if (frontmatter.value.comments === false) return
if (window.EchoThread && typeof window.EchoThread.bootstrap === 'function') {
window.EchoThread.bootstrap()
return
}
if (document.querySelector(`script[src="${WIDGET_SRC}"]`)) return
const script = document.createElement('script')
script.src = WIDGET_SRC
script.async = true
document.body.appendChild(script)
}
onMounted(mount)
watch(() => route.path, () => nextTick(mount))
</script>
<template>
<div
v-if="frontmatter.comments !== false"
id="echothread"
data-api-key="YOUR_API_KEY"
:data-identifier="page.relativePath"
:data-page-title="page.title"
></div>
</template>Replace YOUR_API_KEY with the key from your dashboard; it is a public key designed to sit in page source and only works on the domain you registered.
Three VitePress details are doing the work. Create or edit Run The component already respects MkDocs themes are Jinja templates, and the supported way to change one is In Drop the MkDocs does full page loads, so there is no navigation handling to add. The snippet is the same static markup you would put on any HTML page. Material ships an empty partial for exactly this purpose. Its guide says "The With Material inserts the partial below the content on pages that opt in, and its built-in meta plugin lets a If your Material site uses Docs sites usually have a theme toggle, and the widget can follow it. A reader with a correction can post it as a guest, or sign in with Google, GitHub, X or Facebook. You get threaded replies, a moderation queue where every comment can be held before it appears, and spam scoring on every submission. Comments are stored with EchoThread rather than in your repository, which is the trade against giscus: you lose "the discussion lives next to the code", and you gain readers who are not on GitHub. If most of your readers are on GitHub, the giscus alternative and utterances alternative posts lay out that trade in more detail rather than pretending it does not exist. EchoThread's Hobby plan is free for one site, with comments included, no ads and no reader tracking. Sites created from 1 October 2026 include 10,000 page views a month on Hobby; a site created before that date keeps unmetered page views. Paid plans start at $9 a month for three sites and 100,000 page views; see pricing for the full ladder. For the wider question of whether documentation should have comments at all, and how to moderate them, see adding a comment section to technical documentation. If your docs are on Docusaurus rather than VitePress, the Docusaurus guide covers the equivalent theme swizzle.useData() gives you page.relativePath, the source file's path, which is a stable identifier for the thread even if you later change the URL structure. useRoute() gives you route.path, and the watch on it matters because a component placed in a layout slot is created once and persists across client-side navigation; without the watcher the widget would render on the first page and never again. And everything that touches window or document is inside onMounted or the watcher, which is what the VitePress docs require: "make sure to only access Browser / DOM APIs in beforeMount or mounted hooks" (VitePress runtime API, read on 3 September 2026). The window.EchoThread.bootstrap() is the method the widget exposes for single-page hosts. It tears down the previous instance and mounts into whatever #echothread is on the page now.Step 2: fill the
doc-after slot.vitepress/theme/index.js (or .ts):import { h } from 'vue'
import DefaultTheme from 'vitepress/theme'
import Comments from './Comments.vue'
export default {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'doc-after': () => h(Comments),
})
},
}vitepress build and every page with the doc layout has a comment section after the content and before the footer. If you would rather have it above the previous/next links, use doc-footer-before instead; the slot list is in the VitePress guide linked above.Turning comments off for one page
comments: false in a page's front matter, so the API reference pages that should stay quiet only need:---
comments: false
---MkDocs
custom_dir: "Any file in the custom_dir with the same name as a file in the parent theme will replace the file of the same name in the parent theme" (Customizing your theme, read on 3 September 2026). Instead of replacing a whole file, you extend the base template and override one block.Plain MkDocs, or any theme with a
content blockmkdocs.yml:site_url: https://docs.example.com/
theme:
name: mkdocs
custom_dir: overridessite_url matters: MkDocs builds page.canonical_url, "the full, canonical URL to the current page", from it (MkDocs theme developer guide, read on 3 September 2026). Then create overrides/main.html:{% extends "base.html" %}
{% block content %}
{{ super() }}
{% if page.meta.comments %}
<div id="echothread"
data-api-key="YOUR_API_KEY"
data-page-url="{{ page.canonical_url }}"
data-identifier="{{ page.url }}"
data-page-title="{{ page.title }}"></div>
<script src="https://cdn.echothread.io/widget.js" async></script>
{% endif %}
{% endblock %}{{ super() }} keeps the theme's own content and appends yours after it. page.url is the page's path relative to the site root, stable as long as you do not move the file. The page.meta.comments guard makes comments opt-in per page:---
comments: true
---{% if %} if you want every page to have comments.Material for MkDocs
comments.html partial (empty by default) is the best place to add the snippet", and enables comments per page with the comments: true front-matter property (Adding a comment system, read on 3 September 2026). The same guide documents giscus and lists installing the Giscus GitHub App as the first step, which is the requirement this post exists to avoid.custom_dir: overrides set as above, create overrides/partials/comments.html:{% if page.meta.comments %}
<h2 id="__comments">Comments</h2>
<div id="echothread"
data-api-key="YOUR_API_KEY"
data-page-url="{{ page.canonical_url }}"
data-identifier="{{ page.url }}"
data-page-title="{{ page.title }}"></div>
<script src="https://cdn.echothread.io/widget.js" async></script>
{% endif %}.meta.yml file in a folder set comments: true for everything beneath it, so a whole section can opt in with one file.navigation.instant, pages load without a full refresh, and a script inside the content area may not run again for the next page. Material exposes a document$ observable that fires on every page change. Add a JavaScript file through extra_javascript in mkdocs.yml containing:document$.subscribe(() => {
if (document.getElementById('echothread') && window.EchoThread) {
window.EchoThread.bootstrap()
}
})window.EchoThread.bootstrap() is the method the widget exposes for single-page hosts; it re-mounts into whatever #echothread is on the page now.Theming for documentation
data-theme="light" or data-theme="dark" pins a scheme; data-theme-source="--vp-c-bg" on VitePress, or --md-default-bg-color on Material, reads the current background from the theme's own CSS variable so the widget flips when the reader does. data-accent-color matches your brand, and data-font-family="inherit" uses the docs font. The full attribute list is in the docs.What readers get, and what you keep
What it costs
Discussion
Comments
This thread runs on EchoThread — the same widget you would add to your own site.