Skip to main content

Components

<Markdown components={{ a: AppLink, img: AppImage, code: CodeBlock }}>
{content}
</Markdown>

The components prop maps an element name to a React component. Every element the renderer emits can be replaced. This is the escape hatch of last resort, and it always works.

A link override is the usual first one. It routes internal links through your router and marks external ones.

function DocsLink(props: ComponentProps<'a'>) {
const external = props.href?.startsWith('http')
return <a {...props} target={external ? '_blank' : undefined} rel={external ? 'noreferrer' : undefined} />
}

<Markdown components={{ a: DocsLink }}>{content}</Markdown>
External links get an arrow and rel=noreferrer
Markdown
Read the [specification](https://example.com/spec) or the [changelog](/changelog).
Rendered

Read the specification or the changelog.

Overriding an image

An image override is where you add lazy loading, a CDN, sizing or a caption.

function CaptionedImage({ src, alt, title }: ComponentProps<'img'>) {
return (
<span className="figure">
<img src={src} alt={alt} loading="lazy" />
{title ? <span className="caption">{title}</span> : null}
</span>
)
}

A lone image sits inside a paragraph, and a p cannot legally contain a figure. Use inline elements here, or override p as well and unwrap it yourself.

The image title becomes a caption
Markdown
![The React Markdown Kit logo](/img/logo.svg "Figure 1. The project logo")
Rendered

The React Markdown Kit logoFigure 1. The project logo

Overriding code

A fenced code block renders as pre wrapping code. Override code for syntax highlighting, and pre for the surrounding chrome.

The language is on className as language-js. The rest of the info string is on node.data.meta.

function LabelledCode({ className, node, children }) {
const language = className?.replace('language-', '')
const meta = node?.data?.meta
return (
<>
{language ? <span>{language}{meta ? ` · ${meta}` : ''}</span> : null}
<code className={className}>{children}</code>
</>
)
}
Language and info string, read off the node
Markdown
```ts title="server.ts"
export const port = 8080
```
Rendered
ts · title="server.ts"export const port = 8080

Inline code reaches the same component with no className, so check for a language before treating it as a block.

What an override receives

An override is called with three kinds of prop.

The element's own HTML props. An a gets href and title. An img gets src, alt and title. A fenced code gets className.

children. Already-rendered React children, except for void elements such as img and hr.

node. The hast element this component was built from.

node carries the metadata that has no HTML prop:

FieldHolds
node.tagNameThe element name, such as h2
node.propertiesEvery attribute, before React renaming
node.data.metaThe rest of a code fence info string
node.positionStart and end offsets in the Markdown source
node.childrenThe unrendered child nodes

node.position is useful for linking a rendered heading back to its line in the source. That is how an editor preview highlights the block you are typing in.

Overrides apply everywhere

There is one parser for the whole document, so a table cell is an ordinary subtree. Your a override runs inside a heading, a list item, a table cell and a footnote alike.

One override, four contexts
Markdown
## A [heading link](https://example.com)

- A [list link](https://example.com)

| Cell |
| --- |
| A [table link](https://example.com) |

Stability

node is the hast element, and hast is a public, versioned format. Treat node.properties and node.position as stable.

Extensions may introduce their own node types and contribute their own components. See Extensions.

Reuse one set of overrides across the renderer, the editor preview and templates with a preset.

Styling without replacing components is covered in Styling, including the classNames prop.