Markdown-Vue (MDV) lets you write Vue-style components directly inside Markdown files. Each .md
file can contain:
- Regular Markdown content
- Vue-style templates and components (standard HTML/Vue tags)
- YAML frontmatter for metadata
- Inline
<script>
and<style>
blocks - A small inline component shorthand for short components
This keeps the source human-readable while enabling Vue integration.
- Template-first: Markdown is the base template; sprinkle Vue/HTML where needed.
- Standard HTML / Vue tags: Use normal HTML elements and Vue component tags (
<div>
,<section>
,<UserBadge>
, etc.). MDV follows standard Vue/HTML semantics. - Inline component shorthand: MDV provides a concise inline shorthand for simple, content-driven components.
Use standard HTML elements and Vue component tags. Attributes follow Vue syntax (v-for
, v-if
, :prop
, @click
, etc.).
Example:
<div class="card">
# Title inside the card
<p>This paragraph lives inside the div</p>
</div>
Mustache interpolation ({{ }}
) works anywhere in the template.
Hello, {{ user.name }}
MDV supports a compact inline component shorthand in two forms:
- Slot content:
[text]{ ::ComponentName }
- Dynamic/default-slot expression:
:[user.name]{ ::UserBadge }
Props, directives, and bindings are passed inside the braces:
[Click me]{ ::Button :to="user.url" @click="onClick(user)" v-if="user.active" }
Notes:
- The inline shorthand forms
[text]{ ::Component ... }
and:[expr]{ ::Component }
are the only MDV-specific shorthands. - For multiple named slots or complex layouts prefer full component tags (
<MyComponent>...</MyComponent>
).
Use Vue's v-for
on any element or component. Always include :key
.
Example with HTML tags:
<ul>
<li v-for="(user, i) in users" :key="user.id || i">
<UserBadge>{{ user.name }}</UserBadge>
</li>
</ul>
Inline shorthand in loops:
:[item]{ v-for="item in items" :key="item.id || itemIndex" }
The :
inline form places the evaluated expression into the default slot for the inline component.
Notes:
- Use
:key
with everyv-for
. - Prefer inline loops for small, simple items.
MDV supports dynamic rows inside standard Markdown tables using a { rows }
placeholder. Header is normal Markdown; rows are injected where { rows }
appears. A fallback row is optional.
Example:
| id | name | action |
| ------------- | ---- | ------ |
| No item found |
{ rows }
How it works:
{ rows }
is replaced by rendered rows generated from your data source.- If the data is empty and you provided a fallback row (like
| No item found |
), that row is shown. - Rows may contain inline components or full HTML/Vue tags.
Simple conceptual example:
<!-- body -->
| id | name | action |
| ------------- | ---- | ------ |
| No item found |
{ rows }
<script setup>
const rows = items.map(item => `| ${item.id} | ${item.name} | [Edit]{ ::EditButton :id="${item.id}" } |`).join('\\n')
</script>
Notes:
- Fallback row is optional.
- Keep header as normal Markdown.
You may include <script>
and <style>
blocks inside .md
files. Prefer <script setup>
.
Script example:
<script setup lang="ts">
import UserBadge from './UserBadge.vue'
import { ref } from 'vue'
const users = ref([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
])
</script>
Style example:
<style scoped>
h1 { font-weight: 700 }
</style>
MDV supports YAML frontmatter, compiled into a .meta.json
file alongside the component.
Example frontmatter:
---
title: Hello World
description: This is my first MDV page
---
# {{ $meta.title }}
Emitted files:
HelloWorld.vue
(compiled component)HelloWorld.meta.json
(metadata)
Accessing metadata:
- From script:
useMeta()
always returns a Promise and should beawait
ed.
import { useMeta } from "mdv";
const meta = await useMeta();
const otherMeta = await useMeta("/path/to/other");
- From templates:
$meta
is available ({{ $meta.title }}
).
<v-app>
# My Markdown-Vue App
This is an MDV page with components and markdown.
<section>
<h2>Users</h2>
<ul>
<li v-for="(u, i) in users" :key="u.id || i">
<UserBadge :user="u" />
</li>
</ul>
</section>
</v-app>
<script setup>
import UserBadge from './UserBadge.vue'
import { ref } from 'vue'
const users = ref([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
])
</script>
- Static websites
- Repo-based CMS
- Documentation & Blogs with interactive components
-
Prefer Vue-style attributes (
v-...
and:
) and include:key
onv-for
lists. -
Use the inline shorthand forms only:
[text]{ ::ComponentName ...props ...directives }
:[expression]{ ::ComponentName }
-
Use full HTML/Vue tags for multiple named slots or complex layouts.
-
Keep scripts/styles in-file for small pages; split out for complexity.