Using Vue in Markdown
In VitePress, each markdown file is compiled into HTML and then processed as a Vue Single-File Component. This means you can use any Vue features inside the markdown, including dynamic templating, using Vue components, or arbitrary in-page Vue component logic by adding a <script>
tag.
It is also important to know that VitePress leverages Vue 3's compiler to automatically detect and optimize the purely static parts of the markdown. Static contents are optimized into single placeholder nodes and eliminated from the page's JavaScript payload. They are also skipped during client-side hydration. In short, you only pay for the dynamic parts on any given page.
Templating
Interpolation
Each Markdown file is first compiled into HTML and then passed on as a Vue component to the Vite process pipeline. This means you can use Vue-style interpolation in text:
Input
{{ 1 + 1 }}
Output
2
Directives
Directives also work:
Input
<span v-for="i in 3">{{ i }}</span>
Output
1 2 3
Access to Site & Page Data
You can use the useData
helper in a <script>
block and expose the data to the page.
Input
<script setup>
import { useData } from 'vitepress'
const { page } = useData()
</script>
<pre>{{ page }}</pre>
Output
{
"path": "/using-vue.html",
"title": "Using Vue in Markdown",
"frontmatter": {}
}
Escaping
By default, fenced code blocks are automatically wrapped with v-pre
, unless you have set some language with -vue
suffix like js-vue
(in that case you can use Vue-style interpolation inside fences). To display raw mustaches or Vue-specific syntax inside inline code snippets or plain text, you need to wrap a paragraph with the v-pre
custom container:
Input
::: v-pre
`{{ This will be displayed as-is }}`
:::
Output
{{ This will be displayed as-is }}
Using Components
When you need to have more flexibility, VitePress allows you to extend your authoring toolbox with your own Vue Components.
Importing components in markdown
If your components are going to be used in only a few places, the recommended way to use them is to importing the components in the file where it is used.
<script setup>
import CustomComponent from '../components/CustomComponent.vue'
</script>
# Docs
This is a .md using a custom component
<CustomComponent />
## More docs
...
Registering global components in the theme
If the components are going to be used across several pages in the docs, they can be registered globally in the theme (or as part of extending the default VitePress theme). Check out the Customization Guide for more information.
In .vitepress/theme/index.js
, the enhanceApp
function receives the Vue app
instance so you can register components as you would do in a regular Vue application.
import DefaultTheme from 'vitepress/theme'
export default {
...DefaultTheme,
enhanceApp(ctx) {
DefaultTheme.enhanceApp(ctx)
ctx.app.component('VueClickAwayExample', VueClickAwayExample)
}
}
Later in your markdown files, the component can be interleaved between the content
# Vue Click Away
<VueClickAwayExample />
IMPORTANT
Make sure a custom component's name either contains a hyphen or is in PascalCase. Otherwise, it will be treated as an inline element and wrapped inside a <p>
tag, which will lead to hydration mismatch because <p>
does not allow block elements to be placed inside it.
Using Components In Headers ⚡
You can use Vue components in the headers, but note the difference between the following syntaxes:
Markdown | Output HTML | Parsed Header |
---|---|---|
| <h1>text <Tag/></h1> | text |
| <h1>text <code><Tag/></code></h1> | text <Tag/> |
The HTML wrapped by <code>
will be displayed as-is; only the HTML that is not wrapped will be parsed by Vue.
TIP
The output HTML is accomplished by markdown-it, while the parsed headers are handled by VitePress (and used for both the sidebar and document title).
Using CSS Pre-processors
VitePress has built-in support for CSS pre-processors: .scss
, .sass
, .less
, .styl
and .stylus
files. There is no need to install Vite-specific plugins for them, but the corresponding pre-processor itself must be installed:
# .scss and .sass
npm install -D sass
# .less
npm install -D less
# .styl and .stylus
npm install -D stylus
Then you can use the following in Markdown and theme components:
<style lang="sass">
.title
font-size: 20px
</style>
Script & Style Hoisting
Sometimes you may need to apply some JavaScript or CSS only to the current page. In those cases, you can directly write root-level <script>
or <style>
blocks in the Markdown file. These will be hoisted out of the compiled HTML and used as the <script>
and <style>
blocks for the resulting Vue single-file component:
Built-In Components
VitePress provides Built-In Vue Components like ClientOnly
, check out the Global Component Guide for more information.
Also see:
Browser API Access Restrictions
Because VitePress applications are server-rendered in Node.js when generating static builds, any Vue usage must conform to the universal code requirements. In short, make sure to only access Browser / DOM APIs in beforeMount
or mounted
hooks.
If you are using or demoing components that are not SSR-friendly (for example, contain custom directives), you can wrap them inside the built-in <ClientOnly>
component:
<ClientOnly>
<NonSSRFriendlyComponent />
</ClientOnly>
Note this does not fix components or libraries that access Browser APIs on import. To use code that assumes a browser environment on import, you need to dynamically import them in proper lifecycle hooks:
<script>
export default {
mounted() {
import('./lib-that-access-window-on-import').then((module) => {
// use code
})
}
}
</script>
If your module export default
a Vue component, you can register it dynamically:
<template>
<component
v-if="dynamicComponent"
:is="dynamicComponent">
</component>
</template>
<script>
export default {
data() {
return {
dynamicComponent: null
}
},
mounted() {
import('./lib-that-access-window-on-import').then((module) => {
this.dynamicComponent = module.default
})
}
}
</script>
Also see:
Using Teleports
Vitepress currently has SSG support for teleports to body only. For other targets, you can wrap them inside the built-in <ClientOnly>
component or inject the teleport markup into the correct location in your final page HTML through postRender
hook.
Details
<script setup lang="ts">
import { ref } from 'vue'
const showModal = ref(false)
</script>
<template>
<button class="modal-button" @click="showModal = true">Show Modal</button>
<Teleport to="body">
<Transition name="modal">
<div v-show="showModal" class="modal-mask">
<div class="modal-container">
<p>Hello from the modal!</p>
<div class="model-footer">
<button class="modal-button" @click="showModal = false">
Close
</button>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.modal-mask {
position: fixed;
z-index: 200;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.3s ease;
}
.modal-container {
width: 300px;
margin: auto;
padding: 20px 30px;
background-color: var(--vp-c-bg);
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
transition: all 0.3s ease;
}
.model-footer {
margin-top: 8px;
text-align: right;
}
.modal-button {
padding: 4px 8px;
border-radius: 4px;
border-color: var(--vp-button-alt-border);
color: var(--vp-button-alt-text);
background-color: var(--vp-button-alt-bg);
}
.modal-button:hover {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg);
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
.modal-enter-from .modal-container,
.modal-leave-to .modal-container {
transform: scale(1.1);
}
</style>
<ClientOnly>
<Teleport to="#modal">
<div>
// ...
</div>
</Teleport>
</ClientOnly>