Skip to main content

Use Vue components in Markdown

Render Vue components, props, and slots from Markdown with MDC syntax.

Create a Vue component, then invoke it from Markdown with a ::tag block.

app/components/Callout.vue
<script setup lang="ts">
import { useSlots } from 'vue'

defineProps<{
  title: string
  tone?: 'info' | 'warning'
}>()

const slots = useSlots()
</script>

<template>
  <aside :data-tone="tone ?? 'info'">
    <strong>{{ title }}</strong>
    <div><slot /></div>
    <footer v-if="slots.actions"><slot name="actions" /></footer>
  </aside>
</template>

Allow the component, its props, and its slots in the runtime component policy:

nuxt.config.ts
export default defineNuxtConfig({
  content: {
    componentPolicy: {
      components: {
        callout: {
          kind: 'block',
          props: {
            title: { type: 'string', required: true },
            tone: { type: 'string', required: false }
          },
          slots: ['default', 'actions'],
          media: null
        }
      }
    }
  }
})

The policy is a closed allowlist used by rendering and portability checks. Undeclared components, props, and slots are rejected.

content/guide.md
# Guide

::callout{title="Before you deploy" tone="warning"}
Run the production build and test a direct nested route.
::

The kebab-case tag maps ::callout to Callout.vue. Declared attributes inside {} become props. The block content fills the default slot.

Fill named slots

Open each named slot with #slot-name:

content/features.md
::callout{title="Before you deploy" tone="warning"}

#default
Run the production build and test a direct nested route.

#actions
[Open the deployment guide](/docs/resources/deployment)
::

The slot name must also appear in componentPolicy. Nuxt must discover the component before MDC can render it; app/components/ works with Nuxt's default component discovery.

Keep the words inside each component meaningful on their own. The same Markdown then remains readable in a CMS, a text editor, or another renderer that does not load the Vue component.