Rendering

Render Comark in Vue

Learn how to render Comark in a Vue.js application with custom components, plugins, and streaming support.

The <Comark> component is the simplest way to render markdown in Vue. Pass markdown content directly and it handles parsing and rendering automatically.

Basic Usage

App.vue
<script setup lang="ts">
import { Comark } from '@comark/vue'
import Alert from './Alert.vue'

const content = `# Hello World

This is **markdown** with Comark components.

::alert{type="info"}
This is an alert!
::
`
</script>

<template>
  <Comark :components="{ Alert }">{{ content }}</Comark>
</template>

Props & Slots

PropTypeDefaultDescription
(default slot)string-Markdown content to parse and render
markdownstringundefinedAlternative to default slot - markdown content as prop
optionsParseOptions{}Parser options (autoUnwrap, autoClose, etc.)
pluginsComarkPlugin[][]Array of plugins (highlight, emoji, toc, etc.)
componentsRecord<string, Component>{}Custom Vue component mappings
componentsManifestComponentManifestundefinedDynamic component resolver function
streamingbooleanfalseEnable streaming mode
summarybooleanfalseOnly render content before <!-- more -->
caretboolean | { class: string }falseAppend caret to last text node

Using the markdown Prop

Instead of using the default slot, you can pass markdown content via the markdown prop:

App.vue
<script setup lang="ts">
import { Comark } from '@comark/vue'

const content = '# Hello\n\nThis is **markdown**.'
</script>

<template>
  <Comark :markdown="content" />
</template>

With Parser Options

Use the options prop to configure parser behavior:

App.vue
<script setup lang="ts">
import { Comark } from '@comark/vue'

const content = '# Hello World'
const options = {
  autoUnwrap: true,  // Remove <p> wrappers from single-paragraph containers
  autoClose: true    // Auto-close incomplete syntax
}
</script>

<template>
  <Comark :options="options">{{ content }}</Comark>
</template>

With Plugins

Use the plugins prop to add functionality like syntax highlighting, emoji support, or table of contents:

App.vue
<script setup lang="ts">
import { Comark } from '@comark/vue'
import highlight from '@comark/vue/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'

const content = `
\`\`\`javascript
console.log("Hello, World!")
\`\`\`
`

const plugins = [
  highlight({
    themes: {
      light: githubLight,
      dark: githubDark
    }
  })
]
</script>

<template>
  <Suspense>
    <Comark :plugins="plugins">{{ content }}</Comark>
  </Suspense>
</template>
Async plugins (like highlight) make <Comark> an async component. Wrap it in <Suspense> when using these plugins, or rendering will fail silently.

With Custom Components

App.vue
<script setup lang="ts">
import { Comark } from '@comark/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>
  <Comark :components="components">{{ content }}</Comark>
</template>

Live Editor Example

Editor.vue
<script setup lang="ts">
import { ref } from 'vue'
import { Comark } from '@comark/vue'

const content = ref('# Edit me!\n\nType **markdown** here.')
</script>

<template>
  <div class="editor">
    <textarea v-model="content" />
    <div class="preview">
      <Comark>{{ content }}</Comark>
    </div>
  </div>
</template>

Summary Mode

Render only content before <!-- more -->:

ArticleSummary.vue
<script setup lang="ts">
import { Comark } from '@comark/vue'

const content = `# Article Title

This is the summary shown in listings.

<!-- more -->

This is the full article content.
`
</script>

<template>
  <!-- Only renders "This is the summary shown in listings." -->
  <Comark summary>{{ content }}</Comark>
</template>

Define Comark Component

The defineComarkComponent helper creates a pre-configured Comark component with default options, plugins, and components. This is useful for creating custom Comark components that can be reused throughout your application with consistent configuration.

Basic Usage

comark.ts
import { defineComarkComponent } from '@comark/vue'

// Create a custom Comark component with default configuration
export const MyComark = defineComarkComponent({
  name: 'MyComark',
})

With Plugins

comark.ts
import { defineComarkComponent } from '@comark/vue'
import math from '@comark/vue/plugins/math'
import mermaid from '@comark/vue/plugins/mermaid'

export const DocsComark = defineComarkComponent({
  name: 'DocsComark',
  plugins: [
    math(),
    mermaid(),
  ],
})

With Custom Components

comark.ts
import { defineComarkComponent } from '@comark/vue'
import { Math } from '@comark/vue/plugins/math'
import { Mermaid } from '@comark/vue/plugins/mermaid'
import CustomAlert from './components/CustomAlert.vue'
import CustomCard from './components/CustomCard.vue'

export const AppComark = defineComarkComponent({
  name: 'AppComark',
  components: {
    Math,
    Mermaid,
    alert: CustomAlert,
    card: CustomCard,
  },
})

With Syntax Highlighting

comark.ts
import { defineComarkComponent } from '@comark/vue'
import highlight from '@comark/vue/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'

export const CodeComark = defineComarkComponent({
  name: 'CodeComark',
  plugins: [
    highlight({
      themes: {
        light: githubLight,
        dark: githubDark,
      },
    }),
  ],
})

Complete Configuration Example

comark.ts
import { defineComarkComponent } from '@comark/vue'
import math from '@comark/vue/plugins/math'
import mermaid from '@comark/vue/plugins/mermaid'
import highlight from '@comark/vue/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'
import { Math } from '@comark/vue/plugins/math'
import { Mermaid } from '@comark/vue/plugins/mermaid'
import CustomAlert from './components/CustomAlert.vue'

export const AppComark = defineComarkComponent({
  name: 'AppComark',

  // Plugins
  plugins: [
    math(),
    mermaid(),
    highlight({
      themes: {
        light: githubLight,
        dark: githubDark,
      },
    }),
  ],

  // Custom components
  components: {
    Math,
    Mermaid,
    alert: CustomAlert,
  },
})

Using the Defined Component

Once defined, use it like the standard <Comark> component:

App.vue
<script setup lang="ts">
import { AppComark } from './comark'

const content = `
# Math Example

Inline math: $E = mc^2$

$$
\\int_0^\\infty e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}
$$

::alert{type="info"}
This uses pre-configured components and plugins!
::
`
</script>

<template>
  <!-- All configuration is already included -->
  <AppComark>{{ content }}</AppComark>

  <!-- Can still override per-instance -->
  <AppComark :components="{ alert: DifferentAlert }">{{ content }}</AppComark>
</template>

Configuration Options

interface DefineComarkComponentOptions {
  // Component name (for Vue DevTools)
  name?: string

  // Parser options
  autoUnwrap?: boolean
  autoClose?: boolean

  // Plugins
  plugins?: ComarkPlugin[]

  // Custom components
  components?: Record<string, any>
}

Benefits

  1. Centralized Configuration: Define options once, use everywhere
  2. Consistent Behavior: Same plugins and components across your app
  3. Type Safety: Full TypeScript support
  4. Easy Overrides: Instance-level props can override defaults
  5. Better DevTools: Named components appear in Vue DevTools

Merging Behavior

When using the defined component, configurations are merged:

App.vue
<script setup lang="ts">
import { AppComark } from './comark'
import SpecialAlert from './SpecialAlert.vue'
import emoji from '@comark/vue/plugins/emoji'
</script>

<template>
  <AppComark
    :plugins="[emoji()]"
    :components="{ alert: SpecialAlert }"
  >
    {{ content }}
  </AppComark>
</template>

Merge rules:

  • plugins: Arrays are concatenated (config plugins + prop plugins)
  • components: Props override config (prop components take precedence)
  • Other options: Props override config

Real-World Example

comark.ts
import { defineComarkComponent } from '@comark/vue'
import math, { Math } from '@comark/vue/plugins/math'
import mermaid, { Mermaid } from '@comark/vue/plugins/mermaid'
import highlight from '@comark/vue/plugins/highlight'
import githubLight from '@shikijs/themes/github-light'
import githubDark from '@shikijs/themes/github-dark'
import ProsePre from './components/ProsePre.vue'
import ProseAlert from './components/ProseAlert.vue'

export const DocsComark = defineComarkComponent({
  name: 'DocsComark',

  plugins: [
    math(),
    mermaid(),
    highlight({
      themes: {
        light: githubLight,
        dark: githubDark,
      },
    }),
  ],

  components: {
    Math,
    Mermaid,
    ProsePre,
    alert: ProseAlert,
  },

  autoUnwrap: true,
  autoClose: true,
})
pages/docs/[slug].vue
<script setup lang="ts">
import { DocsComark } from '~/comark'

const { data: page } = await useFetch(`/api/docs/${route.params.slug}`)
</script>

<template>
  <article class="prose">
    <DocsComark>{{ page.content }}</DocsComark>
  </article>
</template>

Custom Components

The <Comark> component supports custom components. Components receive props from the markdown and render children via slots.

Creating a Custom Alert

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>

<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 lang="ts">
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>
App.vue
<script setup lang="ts">
const components = {
  h1: Heading,
  h2: Heading,
  h3: Heading,
}
</script>

<template>
  <Comark :components="components">{{ content }}</Comark>
</template>

TypeScript Support

ComarkWrapper.vue
<script setup lang="ts">
import type { Component } from 'vue'
import { Comark } from '@comark/vue'

interface Props {
  content: string
  components?: Record<string, Component>
}

const props = defineProps<Props>()
</script>

<template>
  <Comark :components="props.components">{{ props.content }}</Comark>
</template>

Next Steps

Copyright © 2026