Vue
Render MDC content in Vue applications with two components: MDC for simple use cases and MDCRenderer for advanced control.
Installation
import { MDC, MDCRenderer } from 'mdc-syntax/vue'
MDC Component
The MDC component is the simplest way to render markdown. Pass markdown content directly and it handles parsing and rendering automatically.
Basic Usage
<script setup>
import { MDC } from 'mdc-syntax/vue'
const content = `# Hello World
This is **markdown** with MDC components.
::alert{type="info"}
This is an alert!
::
`
</script>
<template>
<MDC :markdown="content" />
</template>
Props
| Prop | Type | Description |
|---|---|---|
markdown | string | Markdown content to parse and render |
components | Record<string, Component> | Custom component mappings |
excerpt | boolean | Only render content before <!-- more --> |
With Custom Components
<script setup>
import { MDC } from 'mdc-syntax/vue'
import CustomAlert from './components/Alert.vue'
import CustomCard from './components/Card.vue'
const components = {
alert: CustomAlert,
card: CustomCard,
}
const content = `
::alert{type="warning"}
Important message here
::
::card{title="My Card"}
Card content
::
`
</script>
<template>
<MDC :markdown="content" :components="components" />
</template>
Live Editor Example
<script setup>
import { ref } from 'vue'
import { MDC } from 'mdc-syntax/vue'
const content = ref('# Edit me!\n\nType **markdown** here.')
</script>
<template>
<div class="editor">
<textarea v-model="content" />
<div class="preview">
<MDC :markdown="content" />
</div>
</div>
</template>
Excerpt Mode
Render only content before <!-- more -->:
<script setup>
import { MDC } from 'mdc-syntax/vue'
const content = `# Article Title
This is the excerpt shown in listings.
<!-- more -->
This is the full article content.
`
</script>
<template>
<!-- Only renders "This is the excerpt shown in listings." -->
<MDC :markdown="content" excerpt />
</template>
MDCRenderer Component
The MDCRenderer component renders a pre-parsed Minimark AST. Use this when you need more control over parsing, want to cache parsed results, or are working with streams.
Basic Usage
<script setup>
import { parse } from 'mdc-syntax'
import { MDCRenderer } from 'mdc-syntax/vue'
const content = `# Hello World
This is **markdown** with MDC components.
`
const result = parse(content)
</script>
<template>
<MDCRenderer :body="result.body" />
</template>
Props
| Prop | Type | Description |
|---|---|---|
body | MinimarkTree | Required. The parsed AST to render |
components | Record<string, Component> | Custom component mappings |
componentsManifest | `(name: string) => Promise | null` |
With Custom Components
<script setup>
import { parse } from 'mdc-syntax'
import { MDCRenderer } from 'mdc-syntax/vue'
import CustomAlert from './components/Alert.vue'
const components = {
alert: CustomAlert,
h1: CustomHeading,
h2: CustomHeading,
}
const result = parse(content)
</script>
<template>
<MDCRenderer :body="result.body" :components="components" />
</template>
Streaming Support
MDCRenderer works with parseStreamIncremental for real-time content:
<script setup>
import { ref } from 'vue'
import { parseStreamIncremental } from 'mdc-syntax/stream'
import { MDCRenderer } from 'mdc-syntax/vue'
const body = ref({ type: 'minimark', value: [] })
const isComplete = ref(false)
async function loadContent() {
const response = await fetch('/api/content')
for await (const result of parseStreamIncremental(response.body!)) {
body.value = result.body
if (result.isComplete) {
isComplete.value = true
}
}
}
loadContent()
</script>
<template>
<div>
<MDCRenderer :body="body" />
<div v-if="!isComplete" class="loading">
Streaming content...
</div>
</div>
</template>
Dynamic Component Resolution
For large applications, use componentsManifest for lazy loading:
<script setup>
import { MDCRenderer } from 'mdc-syntax/vue'
function loadComponent(name: string) {
const components: Record<string, () => Promise<any>> = {
alert: () => import('./components/Alert.vue'),
card: () => import('./components/Card.vue'),
tabs: () => import('./components/Tabs.vue'),
}
return components[name]?.() ?? Promise.resolve(null)
}
</script>
<template>
<MDCRenderer
:body="mdcAst"
:components-manifest="loadComponent"
/>
</template>
Vite Glob Import
Auto-generate manifest from a components directory:
// utils/components-manifest.ts
export function createComponentsManifest() {
const modules = import.meta.glob('../components/*.vue')
return (name: string) => {
const loader = modules[`../components/${name}.vue`]
return loader?.() ?? Promise.resolve(null)
}
}
Custom Components
Both MDC and MDCRenderer support custom components. Components receive props from the markdown and render children via slots.
Creating a Custom Alert
<!-- components/Alert.vue -->
<script setup>
defineProps<{
type?: 'info' | 'warning' | 'error' | 'success'
}>()
</script>
<template>
<div class="alert" :class="`alert-${type || 'info'}`" role="alert">
<slot />
</div>
</template>
<style scoped>
.alert { padding: 1rem; border-radius: 0.5rem; }
.alert-info { background: #e3f2fd; }
.alert-warning { background: #fff3e0; }
.alert-error { background: #ffebee; }
.alert-success { background: #e8f5e9; }
</style>
Usage in markdown:
::alert{type="warning"}
This is a warning message!
::
Component with Named Slots
<!-- components/Card.vue -->
<template>
<div class="card">
<div v-if="$slots.header" class="card-header">
<slot name="header" />
</div>
<div class="card-body">
<slot />
</div>
<div v-if="$slots.footer" class="card-footer">
<slot name="footer" />
</div>
</div>
</template>
Usage in markdown:
::card
#header
Card Title
#footer
Footer content
This is the card body.
::
Overriding HTML Elements
Override default element rendering:
<!-- components/Heading.vue -->
<script setup>
const props = defineProps<{
__node?: any
id?: string
}>()
</script>
<template>
<component :is="__node?.[0] || 'h2'" :id="id" class="heading">
<a v-if="id" :href="`#${id}`" class="anchor">#</a>
<slot />
</component>
</template>
<script setup>
const components = {
h1: Heading,
h2: Heading,
h3: Heading,
}
</script>
<template>
<MDC :markdown="content" :components="components" />
</template>
ShikiCodeBlock
Syntax-highlighted code block component using Shiki:
<script setup>
import { ShikiCodeBlock } from 'mdc-syntax/vue'
</script>
<template>
<ShikiCodeBlock
language="typescript"
:theme="{ light: 'github-light', dark: 'github-dark' }"
>
const greeting = "Hello, World!"
</ShikiCodeBlock>
</template>
ProsePre & ProsePreShiki
Enhanced code block components with copy functionality and styled headers. These components are part of the internal prose components system (src/vue/components/) and are used automatically when rendering code blocks.
Component Variants
ProsePre: Code block without syntax highlighting but with copy functionality ProsePreShiki: Code block with Shiki syntax highlighting, copy functionality, and styled header
Features
- Copy to Clipboard: Click the copy button to copy code content
- Language Label: Displays language or filename in the header
- Hover Effects: Copy button appears on hover with smooth transitions
- Loading States: ProsePreShiki shows loading state while highlighter initializes
- Fallback: Graceful fallback to plain code if highlighting fails
- Singleton Highlighter: Shared highlighter instance for performance
These components are available in the source code at packages/mdc-syntax/src/vue/components/ and are used internally by the prose rendering system.
TypeScript Support
<script setup lang="ts">
import type { MinimarkTree } from 'mdc-syntax'
import type { Component } from 'vue'
import { MDC, MDCRenderer } from 'mdc-syntax/vue'
interface Props {
content: string
components?: Record<string, Component>
}
const props = defineProps<Props>()
</script>
<template>
<MDC :markdown="props.content" :components="props.components" />
</template>
Performance Tips
- Use
MDCfor simple cases - It handles parsing internally and is optimized for reactive content. - Use
MDCRendererwith caching - Pre-parse content and cache the AST for repeated renders:
import { shallowRef } from 'vue'
const mdcAst = shallowRef(parse(content).body)
- Lazy load components - Use
componentsManifestfor code splitting. - Use
v-oncefor static content:
<template>
<MDC v-once :value="staticContent" />
</template>
See Also
- React Renderer - React integration
- Parse API - Parsing options and streaming
- MDC Syntax - Markdown component syntax