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: override regular code blocks ​

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

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, themes, minWidth, maxWidth, the top-level codeBlockOptions object, and anything passed through codeBlockProps (theme is the recommended child prop; 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.