@crazydos/vue-markdown

中文文档
1.1.4发布于 2025/07/08MITTypeScriptESM + CJS近 30 天下载 3.3万

Vue component for rendering markdown with unified

原始使用文档
文档目录25 节
  1. Features
  2. Used by
  3. Online example & playground
  4. Migration: for v0.x users:
  5. Use named import
  6. Slot prefix
  7. Install
  8. Unified plugins version
  9. Usages
  10. Basic
  11. Add unified plugin
  12. Rendering GFM
  13. Rendering html
  14. Async Plugin
  15. Custom attributes
  16. Customize tag rendering with scoped slot
  17. Use <slot> in markdown
  18. Security
  19. Documentation
  20. <VueMarkdown> & <VueMarkdownAsync> Props
  21. scoped slot and custom attrs
  22. Attributes aliases
  23. Code content example
  24. Reference
  25. License

@crazydos/vue-markdown

npm version npm downloads network dependents License

This is a package that provide Vue 3+ component to render markdown content with unified pipeline.

And is referenced from react-markdown.

Features

  • 🌴 Use unified to render Markdown (CommonMark)
  • 🪑 Supports rendering of additional elements (i.e: GFM: tables, footnotes, task lists etc.) through remark and rehype plugins.
  • :hammer_and_wrench: Allows customization of tag attributes for markdown elements. (i.e: class, target, rel etc.)
  • 🛃 Enables customization of markdown element rendering through Vue scoped slot.
  • 🚀 Support rendering Vue component in markdown (need rehype-raw)
  • 🛡️ Safely renders markdown to prevent harmful content by rehype-sanitize
  • 📝 Fully typed with TypeScript for better developer experience and type safety.

[!TIP] If this package has been helpful to you, feel free to support me on Buy Me a Coffee ~

Buy Me A Coffee

Used by

network dependents

Online example & playground

For more usage examples and detailed plugin configuration, please refer to the usage examples on Stackblitz.

Open in StackBlitz

Migration: for v0.x users:

Thank you to the users of v0.x for motivating me to continue this project.

It is now recommended to upgrade to version v1.x. There are no significant differences between v1.x and v0.x, but v1.x has upgraded unified and enhanced TypeScript support.

To migrate to v1.x, please make the following adjustments:

Use named import

v0.x:

import VueMarkdown from '@crazydos/vue-markdown'

v1.x

import { VueMarkdown } from '@crazydos/vue-markdown'

Slot prefix

The syntax for the <slot> tag has changed.

Slot name now need add prefix s- as follows:

v0.x:

<!-- in Markdown -->
<slot slot-name="header" />

<!-- Customize in Vue -->
<template #header>
  <CusomHeader></CusomHeader>
</header>

v1.x:

<!-- in Markdown -->
<slot slot-name="header" />

<!-- Customize in Vue -->
<template #s-header>
  <CusomHeader></CusomHeader>
</header>

Install

npm install @crazydos/vue-markdown

Unified plugins version

vue-markdown uses unified 11. so please ensure that the unified plugin you intend to use corresponds to unified 11.

Here are the recommended versions for the plugins:

  • rehype-raw: ^7.0.0
  • remark-gfm: ^4.0.0
  • remark-math: ^6.0.0
  • rehype-katex: ^7.0.1
  • remark-toc: ^9.0.0
  • ...

Usages

Basic

You can pass markdown content through the markdown prop.

<script setup lang="ts">
import { VueMarkdown } from '@crazydos/vue-markdown'
import { ref } from 'vue'

const markdown = ref('## Hello World')
</script>

<template>
  <VueMarkdown :markdown="markdown" />
</template>

Add unified plugin

vue-markdown supports extension through unified plugins. To insert a unified plugin, you can use the corresponding options. vue-markdown also exports helper type to support this.

<script setup lang="ts">
import { type PluggableList, VueMarkdown } from '@crazydos/vue-markdown'
import { ref } from 'vue'

const markdown = ref(`...`)

const remarkPlugins: PluggableList = [
  // ...
]
const rehypePlugins: PluggableList = [
  // ...
]
</script>

<template>
  <!-- Example 1 -->
  <VueMarkdown
    :markdown="markdown"
    :remark-plugins="remarkPlugins"
    :rehype-plugins="rehypePlugins"
  />
  <!-- Example 2: Directly add plugins -->
  <VueMarkdown
    :markdown="markdown"
    :remark-plugins="[
      // ...
    ]"
    :rehype-plugins="[
      // ...
    ]"
  />
</template>

Rendering GFM

By default, vue-markdown only supports basic Markdown syntax (CommonMark). If you need support for other Markdown syntax, you'll need to insert the corresponding unified plugin.

For features like tables, footnotes, task lists, etc., you need to include the remark-gfm plugin. (If you need support for other Markdown syntax like math, please use plugins as well.)

You can use the remark-plugins prop to pass in remark plugins.

npm install remark-gfm
<script setup lang="ts">
import { VueMarkdown } from '@crazydos/vue-markdown'
import remarkGfm from 'remark-gfm'
import { ref } from 'vue'

const markdown = ref(`## Hello World

- [x] Add some task
- [ ] Do some task
`)
</script>

<template>
  <!-- simple usage -->
  <VueMarkdown :markdown="markdown" :remark-plugins="[remarkGfm]" />
</template>

Rendering html

By default, vue-markdown outputs HTML as plain text. If you want to render HTML tags, you need to use the rehype-raw plugin.

npm install rehype-raw
<script setup lang="ts">
import { VueMarkdown } from '@crazydos/vue-markdown'
import rehypeRaw from 'rehype-raw'
import { ref } from 'vue'

const markdown = ref(`
<details>
  <summary>Details</summary>
  Something small enough to escape casual notice.
</details>

`)
</script>

<template>
  <VueMarkdown :markdown="markdown" :rehype-plugins="[rehypeRaw]" />
</template>

Async Plugin

[!NOTE] Support from v2+

@crazydos/vue-markdown supports using Unified async plugins (e.g. @shiki/rehype). When using an async plugin, you must use <VueMarkdownAsync> and wrap it with <Suspense> to correctly render the content. <VueMarkdownAsync> shares the same interface as <VueMarkdown>, including the same props and slots.

<script setup lang="ts">
import rehypeShiki from '@shikijs/rehype'
</script>

<template>
  <Suspense>
    <VueMarkdownAsync
      :markdown="markdown"
      :rehype-plugins="[rehypeShiki, { theme: 'github-light' }]"
    />
  </Suspense>
</template>

Custom attributes

You can customize tags for individual HTML elements, for example, by adding default classes or setting attributes like target, rel, lazyload, etc. The customAttrs will be passed into Vue's h function, so it will have the same effect as passing attributes to a h. Please refer to Vue's official documentation to understand the effects of different data types when passed to the h function.

<script setup lang="ts">
import { type CustomAttrs, VueMarkdown } from '@crazydos/vue-markdown'
import { ref } from 'vue'

const markdown = ref(`
# Hello world

## Hello World 2

[Google](https://www.google.com)

`)

const customAttrs: CustomAttrs = {
  // use html tag name as key
  h1: { class: ['heading'] },
  h2: { class: ['heading'] },
  a: { target: '_blank', rel: 'noopener noreferrer' }
}
</script>

<template>
  <VueMarkdown :markdown="markdown" :custom-attrs="customAttrs" />
</template>

customAttrs provides some advanced functionalities: pass in a function for more flexible configurations.

const customAttrs: CustomAttrs = {
  h1: { class: ['heading'] },
  h2: { class: ['heading'] },
  a: (node, combinedAttrs) => {
    if (
      typeof node.properties.href === 'string'
      && node.properties.href.startsWith('https://www.google.com')
    ) {
      return { target: '_blank', rel: 'noopener noreferrer' }
    }
    else {
      return {}
    }
  }
}
  • Some tags also have aliases, for example, the heading alias can be used for h1 through h6 tags.

For example, if we want to configure it as follows:

const customAttrs: CustomAttrs = {
  h1: { class: ['heading'] },
  h2: { class: ['heading'] },
}

This is equivalent to:

const customAttrs: CustomAttrs = {
  heading: { class: ['heading'] }
}

It can also receive a function for configuration.

// will set
// class="heading heading-1" for h1
// class="heading heading-2" for h2
// .......
const customAttrs: CustomAttrs = {
  heading: (node, combinedAttrs) => {
    return { class: ['heading', `heading-${combinedAttrs.level}`] }
  }
}

Please refer to the doc, check parameter the function can accept.

Customize tag rendering with scoped slot

This feature is a bit more cumbersome in Vue. If customAttrs doesn't meet your needs, you can customize tags through a scoped slot.

<VueMarkdown
  markdown="## hello world"
>
  <template #h2="{ children, ...props }">
    <h1 v-bind="props">
      <Component :is="children" />
    </h1>
  </template>

</VueMarkdown>

If you want to display the text "hello world" in the example, you need to render the children. You can obtain the children from slot props, and it represents the children of the h1. Since I've turned it into a functional component, you must render it using <Component>. You can also render children wherever you want.

The parameters received by the scoped slot are essentially the same. However, some slots may receive special parameters; for example, in the ul element, it might receive depth, indicating the nesting level in a list and specifying which level it is.

<VueMarkdown
  :markdown="`## title

- list 1
  - list 2
    - list 3
`"
>
  <template #ul="{ children, depth, ...props }">
    <ul :class="['unordered-list', `list-depth-${depth}`]">
      <Component :is="children" />
    </ul>
  </template>

</VueMarkdown>

Additionally, similarly, the scoped slot also provides the same HTML tag alias as customAttrs. You can use the alias to insert into the slot.

<template>
  <VueMarkdown
    markdown="
  # hello world

  ## hello world

  ### hello world

  #### hello world

  ##### hello world

  ###### hello world
  "
  >
    <template
      #heading="{
        children,
        level,
        key,
      // ...props
      }"
    >
      <MyCustomHeading :key="key" :level="level">
        <Component :is="children" />
      </MyCustomHeading>
    </template>
  </VueMarkdown>
</template>

For more, please refer to scoped slot

re-render issue

I would recommend keeping the state in the parent component and then passing it down to the Custom Component. When your Markdown changes, especially when implementing an editor feature, the position of elements in the tree nodes can frequently change, causing the components within to be remounted intermittently, resetting their states (even with keys added).

Use <slot> in markdown

You can use the <slot> syntax in Markdown, but you need to configure the following:

  1. setup rehype-raw plugins
  2. set :sanitize="false"
npm install rehype-raw

In the following example, we write Vue-like slot syntax in Markdown and can insert this slot from the parent component. At the same time, you can obtain slot props.

Currently, it does not support Vue template syntax like v-bind (supporting this would be a bit more challenging).

[!WARNING] Before v0.2.0,please use <slot name="custom" /> to specify slot name

<script setup lang="ts">
import { VueMarkdown } from '@crazydos/vue-markdown'
import remarkRaw from 'rehype-raw'
import { ref } from 'vue'

const markdown = ref(`
## Hello Slot

<slot slot-name="custom" msg="Insert from vue component"></slot>
`)
</script>

<template>
  <!-- simple usage -->
  <VueMarkdown :markdown="markdown" :rehype-plugins="[remarkRaw]" :sanitize="false">
    <!-- use 's-' + 'slot-name' -->
    <template #s-custom="{ msg }">
      <span> {{ msg }} </span>
    </template>
  </VueMarkdown>
</template>

Security

To defend against various attacks (e.g., XSS), we use the rehype-sanitize unified plugin internally. To enable it, please add :sanitize="true".

Refer to the rehype-sanitize documentation for configuration details.

Here's an example configuration that prevents the rendering of all HTML tags, displaying only text:

<script setup lang="ts">
import { type SanitizeOptions, VueMarkdown } from '@crazydos/vue-markdown'
import { ref } from 'vue'

const sanitizeOption: SanitizeOptions = {
  // Please pass the parameters of rehype-sanitize into the sanitizeOptions:
  sanitizeOptions: {
    tagNames: [],
  },

  // mergeOptions: Optional. Internally, we use the `deepmerge` package to combine `defaultSchema` and `sanitizeOptions`. You can adjust the merging behavior in `mergeOptions`.
  mergeOptions: {
    arrayMerge: (target, source) => {
      return source
    },
  },
}
</script>

<template>
  <VueMarkdown
    :markdown="content"
    :custom-attrs="customAttrs"
    :remark-plugins="[remarkGfm]"
    :rehype-plugins="[rehypeRaw]"
    :sanitize-options="sanitizeOption"
    sanitize
  />
</template>

Documentation

<VueMarkdown> & <VueMarkdownAsync> Props

PropDescriptionTypeDefaultExample
markdownMarkdown contentstring''
customAttrsYou can set custom attributes for each element, such as href, target, rel, lazyload, etc. The key is the HTML tag name, and the value can either be an object or a function that returns an object. The value will be passed to Vue's h function. You can refer to Vue's official documentation to learn how to configure h.CustomAttrs{}{ img: { lazyload: true } }

{ h1: (node, combinedAttrs) => { return { class: ['title', 'mb-2'] } }}
remarkPluginsRemark plugins. These plugins will be used between remark-parse and remark-rehype. See https://github.com/remarkjs/remark?tab=readme-ov-file#pluginsPluggableList[]
rehypePluginsrehype plugins. These plugins will be used after remark-rehype but before rehype-sanitize. See https://github.com/remarkjs/remark-rehype?tab=readme-ov-file#related PluggableList[]
sanitizeWhether to sanitize the HTML content. (use rehype-sanitize). You need to disable this option if you want to render <slot> in markdown content.booleanfalse
sanitizeOptionsOptions for rehype-sanitize. see: https://github.com/syntax-tree/hast-util-sanitize#schemaSanitizeOptions{ allowDangerousHtml: true }
rehypeOptionsOptions for rehype-parse. see: https://github.com/remarkjs/remark-rehype?tab=readme-ov-file#optionsOmit<TRehypeOptions, 'file'>{}

scoped slot and custom attrs

In both scoped slots and customAttrs, you can receive additional parameters. Besides the HTML attributes that can be set in Markdown, vue-markdown also provides additional parameters.

TagParameterTypeDescription
All ElementsAny HTML attributes on the elementstringalt, title, etc...
h1 ~ h6, headinglevelnumberRepresents heading level
h1 ~ h6, headinglevelnumberRepresents heading level
code, inline-code, block-codelanguagestringRepresents language. e.g: js, sh, md ....
languageOriginalstringTypically in the form of language- followed by the language, for example, language-js, language-sh. Similar to the language parameter, but language provides a simpler representation.
inlinebooleaninline code or block code
contentstringA string which is text content of the code. (v0.3.0+). see code example
th, td, trisHeadbooleanIndicates if it is in thead.
list, ol, ulorderedbooleanIndicates if it is an ordered-list or not.
depthnumberIn nested lists, it indicates the level at which the list is positioned. The first level is 0.
list-item, liorderedbooleanIndicates if it is in an ordered-list or not.
depthnumberIn nested lists, it indicates the level at which the list is positioned. The first level is 0.
indexnumberRepresents the position of the list item within its current level. The first item at any level is considered 0.

Attributes aliases

Attribute aliases can be used in the configuration of both scoped slot and customAttrs. Generally, if you are using aliases for configuration or inserting slots, the alias configuration will take precedence over its corresponding HTML tag.

AliasHTML Tags
headingh1, h2, h3, h4, h5, h6
listol, ul
list-itemli
inline-codecode (inline)
block-codecode (block in pre tag)

Code content example

<!-- For example -->
<template>
  <VueMarkdown :markdown="markdown">
    <template #code="{ children, ...props }">
      <MyCustomCodeBlock :code="props.content" :lang="props.language" />
      <!-- Or -->
      <code>
        <component :is="children" />
      </code>
    </template>
  </VueMarkdown>
</template>

Reference

react-markdown.

License

MIT

版本与发布记录

@crazydos/vue-markdown 共发布 17 个版本,首次发布于 2023/09/28;过去 12 个月发布了 0 个版本。

版本发布日期距今
1.1.4latest2025/07/081.3 年前
1.1.32025/07/011.4 年前
1.1.22025/07/011.4 年前
1.1.12025/06/221.4 年前
1.1.02025/06/161.4 年前
1.0.72025/05/311.4 年前

关于 @crazydos/vue-markdown 的常见问题

@crazydos/vue-markdown 是什么?

Vue component for rendering markdown with unified

如何安装 @crazydos/vue-markdown?

在项目目录运行 npm install @crazydos/vue-markdown,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:vue(^3.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@crazydos/vue-markdown 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@crazydos/vue-markdown 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@crazydos/vue-markdown 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@crazydos/vue-markdown 可以商用吗?

@crazydos/vue-markdown 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@crazydos/vue-markdown 的下载量有多少?

根据 npm 官方下载统计,@crazydos/vue-markdown 在 2026/10/11 – 2026/11/09 这 30 天内被下载 33,229 次,日均约 1,108 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@crazydos/vue-markdown 还在维护吗?

最新版本 1.1.4 发布于 2025/07/08(1.3 年前);至今共发布 17 个版本,其中过去 12 个月发布了 0 个;首次发布于 2023/09/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。