Skip to content

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, or link
  • 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:

ts
import type { 
Component
} from 'vue'
import {
setCustomComponents
} from 'markstream-vue'
declare const
DocImage
:
Component
setCustomComponents
('docs', {
image
:
DocImage
,
})
vue
<script setup lang="ts">
import 
MarkdownRender
from 'markstream-vue'
const
markdown
= '![demo](https://example.com/demo.png)'
</script> <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

KeyWhat it replacesNotes
imageImageNodeGreat first override for lightboxes, captions, or lazy-loading wrappers
linkLinkNodeUseful for analytics, router links, or custom tooltip behavior
code_blockRegular fenced code blocksUsed as the generic fallback after exact language keys and the built-in mermaid / d2 / infographic routes
mermaidMermaid fenced blocksPreferred over code_block when only Mermaid needs a custom renderer
d2D2 and d2lang fenced blocksSame routing idea as Mermaid
infographicInfographic fenced blocksUse when only infographic output changes
inline_codeInline code spansUseful for docs typography or special inline tooling
heading, paragraph, list_item, blockquoteContainer nodesPowerful, 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

vue
<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>
ts
import type { 
Component
} from 'vue'
import {
setCustomComponents
} from 'markstream-vue'
declare const
DocImage
:
Component
setCustomComponents
('docs', {
image
:
DocImage
,
})

Example: switch regular code blocks to MarkdownCodeBlockNode

ts
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: target one fenced language directly

ts
import type { 
Component
} from 'vue'
import {
setCustomComponents
} from 'markstream-vue'
declare const
EChartsBlockNode
:
Component
setCustomComponents
('docs', {
echarts
:
EChartsBlockNode
,
})

This only catches fences whose language is echarts. The routing order for code blocks is:

  • exact language key such as echarts
  • built-in specialized routes such as mermaid, d2, and infographic
  • generic code_block

Example: override Mermaid only

ts
import type { 
Component
} from 'vue'
import {
setCustomComponents
} from 'markstream-vue'
declare const
CustomMermaidBlock
:
Component
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:

  • node
  • loading
  • indexKey
  • customId
  • isDark

The renderer also forwards feature-specific props:

  • regular code block overrides receive stream, theme, darkTheme, lightTheme, monacoOptions, themes, minWidth, maxWidth, and anything passed through codeBlockProps (theme is the recommended field; darkTheme / lightTheme remain for compatibility)
  • Mermaid, D2, and infographic overrides receive the corresponding mermaidProps, d2Props, or infographicProps
  • 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, or inline_code unless 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-id so 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:

ts
import { 
removeCustomComponents
} from 'markstream-vue'
removeCustomComponents
('docs')

This is especially helpful in SPAs, Storybook stories, playgrounds, and test suites.