In VitePress 1.6, how do you render a live Vue component demo inside a Markdown page, and which Markdown-specific rules can break it?
answer
- the page is an SFC
- script block after frontmatter
- name shape matters
- fences are not interpolated
- module styles, not scoped
basics
~20 sAdd a <script setup> block after the frontmatter, import the demo component and use its tag in the Markdown. Keep component names PascalCase or hyphenated, remember code fences are v-pre, and prefer <style module> to <style scoped>.
solid answer
~50 sVitePress compiles each Markdown page into a Vue single-file component with no `<template>`: everything that is not a root-level `<script>` or `<style>` is the template. So a demo page adds `<script setup>` after the frontmatter, imports `ButtonBasic` from `./demos/ButtonBasic.vue`, and writes `<ButtonBasic />` in the Markdown; importing per page lets it be code-split. Components used on most pages can be registered globally in the theme's `enhanceApp` with `app.component`. Rules that bite: a component name must be PascalCase or contain a hyphen, or Markdown treats it as an inline element, wraps it in `<p>` and causes a hydration mismatch; `{{ }}` interpolation works in text but not in fenced code, which is `v-pre` unless the language gets a `-vue` suffix; `<style scoped>` adds attributes to every element on the page, so use `<style module>`. And everything must render during the Node build.
code
md · 19 lines---
title: Button
---
<script setup>
import { ref } from 'vue'
import ButtonBasic from './demos/ButtonBasic.vue'
const clicks = ref(0)
</script>
# Button
<ButtonBasic @click="clicks++" />
Clicked {{ clicks }} times.
<style module>
.note { color: var(--vp-c-text-2); }
</style>go deeper
Recall that a Markdown page can import and use Vue components through a script setup block placed after the frontmatter.
Explain the SFC model of a page, per-page imports versus global registration, and the name, fence and style rules that break demos.
Show how you structure demo components so pages stay code-split, render during the build and never cause hydration mismatches.
Decide how demos, their source and the library code are kept in sync so the docs cannot drift from what ships.
## A Markdown page is a Vue component VitePress compiles each `.md` file to HTML with markdown-it and then hands the result to Vue as a single-file component. The differences from an ordinary `.vue` file: - There is **no `<template>` tag**. All root-level content that is not `<script>` or `<style>` is Markdown, and it becomes the template. - Root-level `<script setup>`, `<script>` and `<style>` blocks work as in an SFC, but they must come **after the frontmatter**. - Vue syntax works in the text: `{{ }}` interpolation, directives such as `v-for` on raw HTML, and component tags. ## Showing a live demo For a component library's docs, each component page renders small demo components that live beside the docs: ```md --- title: Button --- <script setup> import ButtonBasic from './demos/ButtonBasic.vue' </script> # Button <ButtonBasic /> ``` There are two ways to make a component available: | Approach | How | When | |---|---|---| | import per page | `<script setup>` import in the `.md` file | demos used on one or a few pages; code-split per page | | register globally | `app.component('ButtonBasic', ButtonBasic)` in the theme's `enhanceApp` | components used on most pages | The page also has access to VitePress runtime helpers. `useData()` from `vitepress` returns refs such as `page`, `frontmatter`, `site`, `theme` and `isDark`, and templates can use `$frontmatter` directly: `{{ $frontmatter.title }}`. ## Rules that break demos 1. **Component names must be PascalCase or hyphenated.** `<ButtonBasic />` or `<button-basic />` is treated as a component. A lowercase name without a hyphen, like `<buttonbasic />`, is treated as an inline HTML element, so Markdown wraps it in a `<p>`. Block content inside `<p>` is invalid, and the server HTML and the client render disagree: a **hydration mismatch**. 2. **Code fences are not interpolated.** VitePress wraps every fenced code block in `v-pre`, so `{{ count }}` inside a fence shows literally. That is what you want for code samples. To interpolate inside a fence, add `-vue` to the language, such as `js-vue`, at some cost to highlighting. 3. **Escaping in text needs `v-pre`.** To show `{{ }}` in prose, wrap it in an element with `v-pre`, or use a `::: v-pre` container. 4. **Prefer `<style module>` to `<style scoped>`.** Scoped styles in Markdown add a data attribute to every element on the page, bloating it; CSS modules keep styles local without that cost. 5. **Mind components in headings.** `# Button <Badge text="beta" />` renders the component but leaves it out of the parsed title used by the sidebar and outline; wrapping it in backticks shows it as code instead. 6. **Everything must render in Node.** The build prerenders the page, so a demo that touches `window` during setup breaks the build even though `vitepress dev` never noticed. ## Why this model is cheap Because pages are compiled by Vue, static Markdown is separated from dynamic parts. For the first visit, static content is left out of the page's JavaScript and skipped during hydration, so a long guide with one live demo pays only for the demo. ## Organising demos for a whole library One page per component quickly means dozens of demo files. A structure that scales: - Keep demos as small `.vue` files beside the docs, such as `docs/components/demos/ButtonBasic.vue`, each showing one use case. - Import the library in demos the way users will, from its package entry or an alias that points at the source, so the docs exercise the public API. - Give every demo a PascalCase name that states the case: `ButtonLoading`, `ButtonIconOnly`. - Wrap demos in one shared, globally registered `DemoBlock` component that adds a frame, a title and a toggle to show the source. The page then reads like prose with components dropped in, and the source shown next to each demo can be imported from the same file with VitePress's snippet syntax, so the rendered demo and the displayed code never disagree. ## Reading page data in a demo A demo or wrapper can adapt to the page it sits on. `useData()` gives reactive access to the page's `frontmatter`, so a page can set `demoTheme: compact` in its frontmatter and the wrapper reads it. `isDark` reports the current colour mode, which is useful when a demo must restyle itself for dark mode. ## Checklist for a demo page - Script after frontmatter, imports relative to the page. - PascalCase component tags. - Code samples in fences, not in raw HTML. - Styles in `<style module>` or in the demo component. - Demo code safe to render during the build.
- When would you register a demo component globally in the theme instead of importing it per page?When it appears on most pages, such as a shared `DemoBlock` wrapper. Global registration in `enhanceApp` saves the import in every file, but the component is then part of the theme's bundle for every page. Per-page imports keep rarely used demos code-split and loaded only where they appear.
- The default theme's doc styles change how a demo's buttons and tables look. How do you isolate the demo?Wrap it in a `::: raw` container, or put `class="vp-raw"` on its root, which VitePress documents for component-library docs. Style isolation is opt-in: add `postcssIsolateStyles()` from `vitepress` to a PostCSS config in the docs folder so the theme's base styles do not reach `.vp-raw` content.
- How do you show Vue template syntax like {{ value }} in a code sample on a VitePress page?Put it in a fenced code block. VitePress wraps fences in `v-pre`, so the braces are shown literally. In prose, wrap the text in an element with `v-pre` or a `::: v-pre` container; otherwise Vue evaluates the expression.
saying these in an interview costs you the question
- Markdown pages cannot contain Vue components; demos need a separate .vue page.
- A lowercase component tag without a hyphen works the same as a PascalCase one.
- Interpolation inside fenced code blocks is evaluated by default.
- <style scoped> is the recommended way to style a single Markdown page.
- The script block can go anywhere in the page, even before the frontmatter.