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.
<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:
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.
# 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:
::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.