Skip to content

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.

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>
External links get an arrow and rel=noreferrer
Markdown
Read the [specification](https://example.com/spec) or the [changelog](/docs/getting-started).
Rendered

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.

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 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>
    </>
  )
}
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 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:

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

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

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.

The whole syntax on one page you can edit: Renderer demo.

Last updated on