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 { setCustomComponents } from 'markstream-vue'
import DocImage from './DocImage.vue'

setCustomComponents('docs', {
  image: DocImage,
})
vue
<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 blocksDoes not replace mermaid, d2, or infographic blocks
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 { setCustomComponents } from 'markstream-vue'
import DocImage from './DocImage.vue'

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: override Mermaid only

ts
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:

  • node
  • loading
  • indexKey
  • customId
  • isDark

The renderer also forwards feature-specific props:

  • regular code block overrides receive stream, darkTheme, lightTheme, monacoOptions, themes, minWidth, maxWidth, and anything passed through codeBlockProps
  • 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.