Render Comark in Nuxt
The @comark/nuxt module provides zero-config Comark setup for Nuxt:
- Auto-imported components.
~/components/prosedirectory for overriding HTML elements.- Nuxt UI integration.
- SSR/SSG support out of the box.
Installation
pnpm add @comark/nuxtnpm install @comark/nuxtyarn add @comark/nuxtbun add @comark/nuxtexport default defineNuxtConfig({
modules: ['@comark/nuxt']
})<Comark>
The <Comark> component is automatically available in all your templates — no imports needed. It handles parsing and rendering in one step.
Usage
Pass markdown via the default slot or the markdown prop:
<template>
<Comark>{{ content }}</Comark>
</template><template>
<Comark :markdown="content" />
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
markdown | string | undefined | Alternative to default slot |
options | ParseOptions | {} | Parser options (autoUnwrap, autoClose, etc.) |
plugins | ComarkPlugin[] | [] | Array of plugins |
components | Record<string, Component> | {} | Custom Vue component mappings |
componentsManifest | ComponentManifest | undefined | Dynamic component resolver |
streaming | boolean | false | Enable streaming mode |
summary | boolean | false | Only render content before <!-- more --> |
caret | boolean | { class: string } | false | Append caret to last text node |
options
See ParseOptions for available options.
<template>
<Comark :options="{ autoUnwrap: true, autoClose: true }">
{{ content }}
</Comark>
</template>plugins
See ComarkPlugin for available plugins.
<script setup lang="ts">
import highlight from '@comark/vue/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'
const plugins = [
highlight({
themes: { light: githubLight, dark: githubDark }
})
]
</script>
<template>
<Comark :plugins="plugins">{{ content }}</Comark>
</template>For math and mermaid plugins, also pass the companion components:
<script setup lang="ts">
import math, { Math } from '@comark/vue/plugins/math'
import mermaid, { Mermaid } from '@comark/vue/plugins/mermaid'
</script>
<template>
<Comark
:markdown="content"
:components="{ math: Math, mermaid: Mermaid }"
:plugins="[math(), mermaid()]"
/>
</template>components
Use this prop to map custom Vue components to Comark elements and use them in your markdown.
Create a Vue component
Save a component such as components/Alert.vue:
<script setup lang="ts">
defineProps<{
type?: 'info' | 'warning' | 'error' | 'success'
}>()
</script>
<template>
<div class="alert" :class="`alert-${type || 'info'}`" role="alert">
<slot />
</div>
</template>Map the tag to your component
Pass the components prop to <Comark>:
<script setup lang="ts">
import Alert from '~/components/Alert.vue'
import Card from '~/components/Card.vue'
const components = { alert: Alert, card: Card }
</script>
<template>
<Comark :components="components">{{ content }}</Comark>
</template>Use it in your Markdown content
::alert{type="warning"}
This is a warning message!
::h1, pre), place components in ~/components/prose instead. See Overriding HTML Elements.componentsManifest
For lazy-loading components on demand. Components are resolved once and cached.
<script setup lang="ts">
const manifest = (name: string) => {
return import(`./components/prose/${name}.vue`)
}
</script>
<template>
<Comark :components-manifest="manifest">{{ content }}</Comark>
</template>Server-Side Rendering
Comark fully supports multiple rendering modes in Nuxt:
Static Generation
<script setup lang="ts">
// Content is parsed at build time
const content = `# Static Content
This is rendered at build time.
`
</script>
<template>
<Comark>{{ content }}</Comark>
</template>Dynamic SSR
<script setup lang="ts">
// Fetch and render on the server
const { data: content } = await useFetch('/api/article')
</script>
<template>
<Comark v-if="content">{{ content }}</Comark>
</template>Prerendering
export default defineNuxtConfig({
modules: ['@comark/nuxt'],
nitro: {
prerender: {
routes: ['/blog', '/docs']
}
}
})Overriding HTML Elements
Place Vue components in ~/components/prose to override how native HTML elements render — automatically registered, no imports or components prop needed.
Directory Structure
~/components/
prose/
ProseH1.vue
ProseH2.vue
ProsePre.vue
ProseA.vueExample
<script setup lang="ts">
defineProps<{
id?: string
}>()
</script>
<template>
<h1 :id="id" class="custom-heading">
<a v-if="id" :href="`#${id}`">#</a>
<slot />
</h1>
</template>Resolution Order
When Comark encounters a tag, it looks for a matching component in this order, stopping at the first match:
Prose{PascalTag}— e.g.,ProseH1forh1. Nuxt auto-registers all components from~/components/prosematching this convention.{PascalTag}— e.g.,Alertforalert. PascalCase version of the tag name.{tag}— e.g.,alert. Exact tag name as-is.- Global — any component registered via
app.component()or Nuxt auto-imports.
If none match, the tag renders as a native HTML element.
Component Bindings
Comark automatically bridges the gap between Comark syntax and your component's interface.
Prop Binding
Attributes in Comark syntax are passed as props to your component. Use the : prefix to pass typed values:
| Markdown | Prop value |
|---|---|
{type="warning"} | "warning" (string) |
{:count="5"} | 5 (number) |
{:active="true"} | true (boolean) |
{:config='{"key":"val"}'} | { key: 'val' } (object) |
Named Slots
Named slots in Comark (#slotname) map to Vue named slots:
- Default slot →
<slot /> - Named slots →
<slot name="slotname" />(e.g.,#footer→<slot name="footer" />)
<script setup lang="ts">
defineProps<{
title?: string
}>()
</script>
<template>
<div class="card">
<h3 v-if="title">{{ title }}</h3>
<slot />
<footer>
<slot name="footer" />
</footer>
</div>
</template>::card{title="My Card"}
Default slot content.
#footer
Footer slot content.
::Nuxt UI Integration
When @nuxt/ui is installed, Comark automatically uses Nuxt UI's prose components for enhanced styling.
@comark/nuxt module detects @nuxt/ui in your dependencies and configures prose components automatically.export default defineNuxtConfig({
modules: ['@comark/nuxt', '@nuxt/ui']
})CSS Setup
@import "tailwindcss";
@import "@nuxt/ui";export default defineNuxtConfig({
modules: ['@comark/nuxt', '@nuxt/ui'],
css: ['~/assets/css/main.css']
})Streaming
Enable real-time rendering as content arrives — ideal for AI chat interfaces and live previews.
Setup
Set streaming to true while content is being received, then false when done:
<script setup lang="ts">
const content = ref('')
const isStreaming = ref(false)
async function askAI(prompt: string) {
content.value = ''
isStreaming.value = true
const response = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ prompt }),
})
const reader = response.body!.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
content.value += decoder.decode(value, { stream: true })
}
isStreaming.value = false
}
</script>
<template>
<Comark :streaming="isStreaming" caret>
{{ content }}
</Comark>
</template>autoClose is enabled by default — incomplete syntax like **bold text is automatically closed on every parse. Disable with :options="{ autoClose: false }".Caret
The caret prop appends a blinking cursor to the last text node while streaming is true:
<!-- Default caret -->
<Comark :streaming="isStreaming" caret>{{ content }}</Comark>
<!-- Custom caret class -->
<Comark :streaming="isStreaming" :caret="{ class: 'my-caret' }">{{ content }}</Comark>.my-caret {
display: inline-block;
width: 2px;
height: 1em;
background: currentColor;
animation: blink 1s step-end infinite;
vertical-align: text-bottom;
}
@keyframes blink {
50% { opacity: 0; }
}TypeScript Support
Use ComarkPlugin from comark to type plugin arrays, and ComarkElement to type the __node prop in components that override HTML elements:
<script setup lang="ts">
import type { Component } from 'vue'
import type { ComarkPlugin } from 'comark'
interface Props {
content: string
components?: Record<string, Component>
plugins?: ComarkPlugin[]
}
const props = defineProps<Props>()
</script>
<template>
<Comark :components="props.components" :plugins="props.plugins">
{{ props.content }}
</Comark>
</template><script setup lang="ts">
import type { ComarkElement } from 'comark'
defineProps<{
__node?: ComarkElement
id?: string
}>()
</script>