logoAnt Design X

⌘ K
DesignDevelopmentComponentsX MarkdownX SDKX CardX SkillPlayground
  • Introduction
  • Code Examples
    2.0.0
  • Playground
  • Themes
  • Streaming Rendering
  • Components
    • Overview
    • ChatThink · Sources
    • ChartsMermaid · AntV
    • CodeCodeHighlighter
  • Plugins
    • Overview
    • Latex
    • CustomPlugins

Overview

The components property is the primary extension point in @ant-design/x-markdown. It lets you map Markdown/HTML nodes to your own React components so you can control rendering, streaming behavior, and business data interaction in one place. To extend further, see Plugins and custom renderers.

Basic registration

tsx
import React from 'react';
import { Mermaid, Think, XMarkdown } from '@ant-design/x';
<XMarkdown
components={{
think: Think,
mermaid: Mermaid,
}}
/>;

ComponentProps

PropertyDescriptionTypeDefault
domNodeComponent DOM node from html-react-parser, containing parsed DOM node informationDOMNode-
streamStatusStreaming rendering supports two states: loading indicates content is being loaded, done indicates loading is complete. Currently only supports HTML format and fenced code blocks. Since indented code has no clear end marker, it always returns done status'loading' | 'done'-
childrenContent wrapped in the component, containing the text content of DOM nodesReact.ReactNode-
restComponent properties, supports all standard HTML attributes (such as href, title, className, etc.) and custom data attributesRecord<string, any>-

Passing extra props

Custom components often need business data (theme, callbacks, etc.). Passing it via an inline function creates a new component reference on every render, so React unmounts and remounts the whole subtree — losing internal state and hurting performance in streaming scenarios. Use componentsProps to pass extra props while keeping component references stable:

tsx
import React, { useMemo } from 'react';
import { XMarkdown } from '@ant-design/x';
// ❌ Inline function: a new component type every render, the subtree is rebuilt
<XMarkdown
components={{
'custom-chart': (props) => <CustomChart {...props} theme={theme} onSelect={onSelect} />,
}}
/>;
// ✅ Stable component reference; extra data flows through componentsProps
const components = { 'custom-chart': CustomChart };
const componentsProps = useMemo(() => ({ 'custom-chart': { theme, onSelect } }), [theme, onSelect]);
<XMarkdown components={components} componentsProps={componentsProps} />;

componentsProps is keyed by tag name. Its props are merged with the parsed HTML attributes and passed to the component:

  • On conflict componentsProps wins — a title in componentsProps overrides title="..." from the HTML.
  • className / class is the exception: both sides are concatenated, with the componentsProps class name first.
  • Internally computed props — domNode, streamStatus, children (plus lang and block for code) — cannot be overridden and are ignored if present in componentsProps.

When componentsProps changes, the component receives a normal props update without being remounted. Like components, it takes part in the render cache, so passing an inline object literal invalidates that cache on every render and re-parses the whole tree — keep the reference stable with useMemo as shown above.

Best Practices

  1. Keep component references stable. Avoid inline function components in components; use componentsProps to pass extra data.
  2. Use streamStatus to separate loading UI (loading) from finalized UI (done).
  3. If data depends on complete syntax, fetch or parse after streamStatus === 'done'.
  4. Keep custom tags semantically clear and avoid ambiguous mixed Markdown/HTML blocks.

FAQ: Custom Tag Closing Issues

If block-level custom tags contain unexpected blank lines, Markdown parsers may end the HTML block early and convert trailing content into paragraphs. To avoid this:

  1. Keep content inside custom tags contiguous when possible.
  2. Or place blank lines both before and after the full custom block so the parser treats it as an independent block.