Override Built-in Components
Most advanced customization starts with setCustomComponents, not parser hooks.
Use this page when you want to:
- replace built-in renderers such as
image,code_block,mermaid, orlink - keep overrides scoped to one part of the app
- understand which override key matches which node type
If you want trusted custom tags such as thinking, go to Custom Tags & Advanced Components. If props and slots are enough, stay with Props & Options.
1. Prefer scoped overrides
Use custom-id on the renderer and register overrides under the same id:
import { setCustomComponents } from 'markstream-vue'
import DocImage from './DocImage.vue'
setCustomComponents('docs', {
image: DocImage,
})<template>
<MarkdownRender custom-id="docs" :content="markdown" />
</template>Why this is the default recommendation:
- it prevents one project area from changing another
- it makes testing and cleanup easier
- it matches how the renderer merges scoped and global mappings
The single-argument form setCustomComponents({ ... }) is still supported, but it is best treated as a legacy global override.
2. The override keys you will actually use
| Key | What it replaces | Notes |
|---|---|---|
image | ImageNode | Great first override for lightboxes, captions, or lazy-loading wrappers |
link | LinkNode | Useful for analytics, router links, or custom tooltip behavior |
code_block | Regular fenced code blocks | Does not replace mermaid, d2, or infographic blocks |
mermaid | Mermaid fenced blocks | Preferred over code_block when only Mermaid needs a custom renderer |
d2 | D2 and d2lang fenced blocks | Same routing idea as Mermaid |
infographic | Infographic fenced blocks | Use when only infographic output changes |
inline_code | Inline code spans | Useful for docs typography or special inline tooling |
heading, paragraph, list_item, blockquote | Container nodes | Powerful, but you must render children yourself |
The full list of built-in node types lives in Components API and the exported CustomComponents type.
3. Start with leaf-like nodes
Leaf-like nodes are the easiest place to begin because they do not require you to manually re-render child nodes.
Example: replace image
<script setup lang="ts">
import type { ImageNodeProps } from 'markstream-vue'
const props = defineProps<ImageNodeProps>()
</script>
<template>
<figure class="docs-image">
<img :src="props.node.src" :alt="props.node.alt" loading="lazy">
<figcaption v-if="props.node.title || props.node.alt">
{{ props.node.title || props.node.alt }}
</figcaption>
</figure>
</template>import { setCustomComponents } from 'markstream-vue'
import DocImage from './DocImage.vue'
setCustomComponents('docs', {
image: DocImage,
})Example: switch regular code blocks to MarkdownCodeBlockNode
import { MarkdownCodeBlockNode, setCustomComponents } from 'markstream-vue'
setCustomComponents('docs', {
code_block: MarkdownCodeBlockNode,
})This only changes regular fenced code blocks. Mermaid, D2, and infographic blocks still route to their specialized renderers unless you override mermaid, d2, or infographic.
Example: override Mermaid only
import { setCustomComponents } from 'markstream-vue'
import CustomMermaidBlock from './CustomMermaidBlock.vue'
setCustomComponents('docs', {
mermaid: CustomMermaidBlock,
})This is the safest pattern when you want custom export controls, branding, or diagram chrome without affecting all code blocks.
4. What props your override receives
Every custom renderer receives the basics:
nodeloadingindexKeycustomIdisDark
The renderer also forwards feature-specific props:
- regular code block overrides receive
stream,darkTheme,lightTheme,monacoOptions,themes,minWidth,maxWidth, and anything passed throughcodeBlockProps - Mermaid, D2, and infographic overrides receive the corresponding
mermaidProps,d2Props, orinfographicProps - link and list overrides inherit tooltip and typewriter-related bindings from the parent renderer
The exported prop types in markstream-vue are the easiest way to keep your overrides strongly typed.
5. Container nodes need extra care
Overriding heading, paragraph, list_item, or blockquote is absolutely supported, but those nodes carry children. Your component needs to decide how to render them.
Practical advice:
- start with
image,link,code_block,mermaid, orinline_codeunless you truly need container-node control - if you override a container node, inspect the built-in renderer in
src/components/*and mirror the child-rendering pattern you need - keep the override scoped with
custom-idso you can iterate safely
6. Cleanup for dynamic scopes
If the override only lives for a temporary view, remove it when that scope is no longer needed:
import { removeCustomComponents } from 'markstream-vue'
removeCustomComponents('docs')This is especially helpful in SPAs, Storybook stories, playgrounds, and test suites.