Skip to content

DemoPreview

Embed runnable component demos in Markdown: the top half renders the component live, the bottom half shows its source. Three modes are supported — directory import, file import, and inline code — with the source panel inheriting the theme's code block styling (beam border, syntax highlighting, folding).

Features

  • Three import modes — directory, file, inline code
  • Source panel reuses theme styling — inherits beam border, syntax highlighting, folding
  • Multi-file tabs — directory mode shows multiple files as tabs
  • Style isolation — the preview area uses not-prose to isolate demo components from doc styles
  • Zero config — auto-registered with the theme, just use <DemoPreview> in Markdown

Directory Import

For multi-file demos. Specify a directory; index.vue runs as the demo, other files show as source tabs.

md
<DemoPreview dir="demos/alert" />
  • dir is relative to the project root (VitePress cwd)
  • Directory must contain index.vue
  • Files sorted with index.vue first, then alphabetically

File Import

Import a single .vue file as the demo.

md
<DemoPreview src="./MyButton.vue" />
  • src is relative to the current Markdown file

Inline Code

Write Vue code directly in Markdown, wrapped in a <DemoPreview> tag. The code is compiled via a Vite virtual module (no temp files on disk).

md
<DemoPreview>
<script setup>
  import { ref } from 'vue'
  const count = ref(0)
</script>
<button @click="count++">Clicked {{ count }} times</button>
</DemoPreview>

Syntax convention

Code inside <DemoPreview> must follow these indentation rules:

  • Top-level tags flush-left: <script setup>, </script>, root elements (e.g. <button>) — no indentation
  • Inner code indented normally: import, const, etc. follow standard Vue SFC conventions

Wrong (causes indentation issues):

md
<DemoPreview>
  <script setup>
  import { ref } from 'vue'
  const count = ref(0)
  </script>
  <button @click="count++">{{ count }}</button>
</DemoPreview>

Correct:

md
<DemoPreview>
<script setup>
  import { ref } from 'vue'
  const count = ref(0)
</script>
<button @click="count++">{{ count }}</button>
</DemoPreview>

Live Example

A file import demo rendered live (inline code example on the test page):

How It Works

Three parts collaborate:

  • Markdown plugin (fxDemoPreviewPlugin) — recognizes the three syntaxes at compile time, rewrites them into <DemoPreview> component calls, auto-injects component imports
  • Vite virtual module plugin (fxDemoVirtualPlugin) — provides SFC content for inline code's import 'virtual:fx-demo/<hash>.vue', compiled by vue (no disk temp files)
  • Preview container (DemoPreview.vue) — top half renders the demo in <ClientOnly> + not-prose, bottom half PreviewGroup shows source

Source display uses VitePress native code block rendering, so it inherits the theme's code block visuals (dark mode beam border, syntax highlighting, folding, etc.).

Notes

  • Demo components run on the client (ClientOnly); SSG shows a loading placeholder
  • The preview area uses not-prose isolation — not affected by vp-doc global styles
  • Inline <script setup> is extracted to the virtual module's top level (Vue disallows script setup inside tags)

Released under the MIT License