Components
<Markdown components={{ a: AppLink, img: AppImage, code: CodeBlock }}>
{content}
</Markdown>The components prop maps an element name to a React component, and you can replace any element the renderer outputs. When nothing else fits, this should always work.
Overriding a link#
A link is usually the first thing people override. This one 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>Read the [specification](https://example.com/spec) or the [changelog](/docs/getting-started).
Read the specification ↗ or the changelog.
Overriding an image#
An image override is the place to 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 still sits inside a paragraph, and a p can't legally contain a figure. Use inline elements here, or override p too and unwrap it yourself.

Figure 1. The project logo
Overriding code#
A fenced code block renders as pre wrapping code. Override code for syntax highlighting and pre for the chrome around it.
The language comes through on className as language-js, and 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>
</>
)
}```ts title="server.ts" export const port = 8080 ```
ts · title="server.ts"export const port = 8080
Inline code goes to the same component without a className, so check for a language before treating it as a block.
What an override receives#
An override gets three kinds of props.
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. The already-rendered React children, except on void elements such as img and hr.
node. The hast element the component was built from.
node has the metadata that doesn't map to an HTML prop:
| Field | Holds |
|---|---|
node.tagName | The element name, such as h2 |
node.properties | Every attribute, before React renaming |
node.data.meta | The rest of a code fence info string |
node.position | Start and end offsets in the Markdown source |
node.children | The unrendered child nodes |
node.position is handy for linking a rendered heading back to its line in the source. An editor preview can use it to highlight the block you're typing in.
Overrides apply everywhere#
One parser handles the whole document, so a table cell is just an ordinary subtree. Your a override runs the same way inside a heading, a list item, a table cell or a footnote.
## 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, so it's fine to rely on node.properties and node.position.
Extensions can add their own node types and contribute their own components. See Extensions.
Related#
To reuse one set of overrides across the renderer, the editor preview and the variables plugin, put them in a preset.
If you only want to style things without replacing components, see Styling, which also covers the classNames prop.
Read next#
The whole syntax on one page you can edit: Renderer demo.
Last updated on