MDC Syntax Logo
Rendering

Vue

Learn how to use MDC Syntax in a Vue application.

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

PropTypeDescription
markdownstringMarkdown content to parse and render
componentsRecord<string, Component>Custom component mappings
excerptbooleanOnly 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

PropTypeDescription
bodyMinimarkTreeRequired. The parsed AST to render
componentsRecord<string, Component>Custom component mappings
componentsManifest`(name: string) => Promisenull`

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

  1. Use MDC for simple cases - It handles parsing internally and is optimized for reactive content.
  2. Use MDCRenderer with caching - Pre-parse content and cache the AST for repeated renders:
import { shallowRef } from 'vue'

const mdcAst = shallowRef(parse(content).body)
  1. Lazy load components - Use componentsManifest for code splitting.
  2. Use v-once for static content:
<template>
  <MDC v-once :value="staticContent" />
</template>

See Also