Participate in the WeaveFox "AI Artist" contest to win SEE Conf tickets and thousands of prizes

logoAnt Design X

DesignDevelopmentComponentsX MarkdownX SDKPlayground
  • Introduction
  • Code Examples
  • Themes
  • Streaming
    • Syntax Processing
    • Animation Effects
  • Components
    • Overview
    • CodeHighlighter
    • Mermaid
    • Think
    • DataChart
    • Sources
    • Custom Component
  • Plugins
    • Overview
    • Latex
    • CustomPlugins

Overview

The components property allows you to replace standard HTML tags with custom React components.

Basic Usage

tsx
import React from 'react';
import { XMarkdown } from '@ant-design/x-markdown';
const CustomHeading = ({ children, ...props }) => (
<h1 style={{ color: '#1890ff' }} {...props}>
{children}
</h1>
);
const App = () => <XMarkdown content="# Hello World" components={{ h1: CustomHeading }} />;

Performance Optimization

1. Avoid Inline Component Definitions

tsx
// ❌ Bad: Creates new component on every render
<XMarkdown components={{ h1: (props) => <h1 {...props} /> }} />;
// ✅ Good: Use predefined component
const Heading = (props) => <h1 {...props} />;
<XMarkdown components={{ h1: Heading }} />;

2. Use React.memo

tsx
const StaticContent = React.memo(({ children }) => <div className="static">{children}</div>);

Streaming Rendering Handling

XMarkdown will pass the streamStatus prop to components by default, indicating whether the component is closed, which is useful for handling streaming rendering.

Status Determination

tsx
const StreamingComponent = ({ streamStatus, children }) => {
if (streamStatus === 'loading') {
return <div className="loading">Loading...</div>;
}
return <div>{children}</div>;
};

Data Fetching Example

Components support two data fetching methods: directly parsing data from Markdown, or initiating network requests independently.

Data Fetching

tsx
const UserCard = ({ domNode, streamStatus }) => {
const [user, setUser] = useState(null);
const username = domNode.attribs?.['data-username'];
useEffect(() => {
if (username && streamStatus === 'done') {
fetch(`/api/users/${username}`)
.then((r) => r.json())
.then(setUser);
}
}, [username, streamStatus]);
if (!user) return <div>Loading...</div>;
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<span>{user.name}</span>
</div>
);
};

Supported Tag Mapping

Standard HTML Tags

TagComponent Name
aa
h1-h6h1-h6
pp
imgimg
tabletable
ul/ol/liul/ol/li
code/precode/pre

Custom Tags

tsx
// Support any custom tags
<XMarkdown
components={{
'my-component': MyComponent,
'user-card': UserCard,
}}
/>

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

FAQ

Block-level HTML Tags Not Properly Closed

When block-level HTML tags contain empty lines (\n\n) internally, the Markdown parser treats empty lines as the start of new paragraphs, thereby interrupting recognition of the original HTML block. This causes closing tags to be incorrectly parsed as inline HTML or plain text, ultimately breaking the tag structure.

Example Problem:

Input Markdown:

markdown
<think>
This is thinking content
The thinking content contains empty lines </think>
This is main content

Incorrect Output:

html
<think>
This is thinking content
<p>The thinking content contains empty lines</p>
<p>This is main content</p>
</think>

Root Cause: According to CommonMark specification, HTML block recognition depends on strict formatting rules. Once two consecutive line breaks (i.e., empty lines) appear inside an HTML block and do not meet specific HTML block type continuation conditions (such as

,
, etc.), the parser will terminate the current HTML block and process subsequent content as Markdown paragraphs.

Custom tags (like ) are typically not recognized as "paragraph-spanning" HTML block types, making them highly susceptible to empty line interference.

Solutions:

  1. Option 1: Remove all empty lines inside tags
markdown
<think>
This is thinking content
The thinking content has no empty lines
</think>
  1. Option 2: Add empty lines before, after, and inside HTML tags to make them independent blocks
markdown
<think>
This is thinking content
The thinking content contains empty lines
</think>