Components

components maps an element to the React component that draws it, the map react-markdown made familiar:

TSX
import { Markdown, type Components } from '@localix/flowdown'
import { Link } from './router'

const components: Components = {
  a: ({ node, href = '', ...props }) =>
    href.startsWith('/') ? (
      <Link to={href} {...props} />
    ) : (
      <a href={href} target="_blank" rel="noreferrer" {...props} />
    ),
}

;<Markdown components={components}>{text}</Markdown>

Each component receives the element's attributes as props, its children, and node, the node of the parsed tree it was made from. Leave node out of what you spread onto the DOM.

What is drawn by default

ElementDrawn byWhat it adds
blockquoteCalloutA > [!NOTE]-style quote becomes aside.fd-callout with role="note". Other quotes stay quotes.
tableTableA div.fd-table around it that scrolls sideways.
answer-tabsTabsThe [!TABS] form: a tab list with the WAI-ARIA tabs keys.
answer-tabTabOne tab's panel; its label is in data-label.
preCodeBlockfigure.fd-code with the language, a download and a copy button.
codeCodeHighlighting, when a highlighter is set.
```mermaidDiagramThe diagram.
```treeFileTreeA directory tree with folders that open and close.

Every other element (p, a, img, ol, h2, …) is the plain HTML element.

Your components replace these, with one exception: ```mermaid and ```tree fences never reach your code or pre. A pre you pass draws every other fenced block, and a code you pass draws every other piece of code. To draw the two fences yourself, pass diagram and tree:

TSX
import { Markdown, type MarkdownComponents } from '@localix/flowdown'

const components: MarkdownComponents = {
  // `growing`: this is the fence still being written. Every diagram above it is complete.
  diagram: ({ source, growing }) => (growing ? <Placeholder /> : <MyDiagram source={source} />),
  tree: ({ source }) => <MyTree source={source} />,
}

<Markdown streaming={!done} components={components}>{reply}</Markdown>

Wrap a built-in component

The built-in components are exported, so you can add to them instead of starting over. A code block with a "Run" button, keeping the language bar, the copy button and highlighting:

TSX
import { CodeBlock, childrenToText, getLanguage, type Components } from '@localix/flowdown'

const components: Components = {
  pre: (props) => {
    const code = props.children as React.ReactElement<{ className?: string; children?: unknown }>
    const language = getLanguage(code.props.className)
    return (
      <div className="runnable">
        <CodeBlock {...props} />
        {language === 'sql' && (
          <button onClick={() => runQuery(childrenToText(code.props.children as never))}>Run</button>
        )}
      </div>
    )
  },
}

Code inside your pre still highlights: the inner code is drawn by Code unless you replace it too.

Style the dialect's elements

The forms are plain HTML elements with fd- classes and data attributes, so most changes are CSS; see Styling. To change their markup, override the HTML element and check the class:

FormElement
Calloutblockquote with data-callout and data-callout-title
Stepsol.fd-steps, each item a div.fd-step-head (title, meta) and div.fd-step-body
Statsul.fd-stats, each item a div.fd-stat
Pros and consdiv.fd-verdicts, each side an aside.fd-verdict with data-verdict="pros" or "cons"
Tabsanswer-tabs with answer-tab children
Folded sectiondetails.fd-details with its summary
Sourcesul.fd-sources, each item a div.fd-source holding the link
Code file namedata-title on the fence's code
Quote sourcefooter.fd-quote-source
Figurefigure.fd-figure with a figcaption
Section numberspan.fd-section-number at the start of a heading

A form with a title (> [!STEPS] Plan) is wrapped in div.fd-form, the title a p.fd-steps-title (fd-tabs-title, fd-stats-title, …) before the form.

TSX
const components: Components = {
  ol: ({ node, className, ...props }) =>
    className === 'fd-steps' ? (
      <ol className={`${className} my-timeline`} {...props} />
    ) : (
      <ol className={className} {...props} />
    ),
}

A callout's component receives the kind in data-callout (note, tip, important, warning, caution) and the author's title, if any, in data-callout-title:

TSX
import { Callout, type Components } from '@localix/flowdown'

const components: Components = {
  blockquote: (props) =>
    props['data-callout'] === 'caution' ? <DangerBanner>{props.children}</DangerBanner> : <Callout {...props} />,
}

Use the components on their own

FileTree draws a tree from the body of a ```tree fence anywhere in your page:

TSX
import { FileTree } from '@localix/flowdown'

;<FileTree source={'src/\n├── index.ts\n└── app/\n    └── App.tsx'} />

Pass getIcon(row, open) to choose the icon of each row; row is a TreeRow with its name, depth, path, folder and comment.

Diagram takes source and draws it with the Mermaid loader of the <Markdown> it is inside; outside one, it shows the source.

Helpers

HelperReturns
getLanguage(className)The language of a language-* class: 'ts' for language-ts.
childrenToText(children)The text of React children, for copying or measuring.
isCodeBlock(children, className)Whether a code element is a block (it has a language or a newline) rather than inline code.

Labels

The renderer adds a few words of its own: names of callouts for screen readers, button labels, diagram notes. Translate them with labels; every key is optional:

TSX
<Markdown
  labels={{
    copy: 'Code kopieren',
    copied: 'Kopiert',
    callouts: { warning: 'Warnung' },
    diagramFailed: (reason) => `Diagramm nicht darstellbar: ${reason}`,
  }}
>
  {text}
</Markdown>
KeyDefaultWhere
callouts.noteNoteA callout's name, read by screen readers. Not printed.
callouts.tipTip
callouts.importantImportant
callouts.warningWarning
callouts.cautionCaution
variantsVariantsThe tab list's name.
fileTreeFile treeThe tree's name.
scrollableTableScrollable tableA table wider than the reply: the box then scrolls and is a Tab stop.
diagramDiagramA drawn diagram's name.
drawingDiagramDrawing diagram…Shown while Mermaid loads and lays out.
diagramFailed(reason) => `Diagram could not be drawn: ${reason}` Shown when the source does not parse.
copyCopy codeThe copy button.
copiedCopiedA copy button after a copy (code, a diagram's source).
downloadDownload codeA code block's download button: saves the code as a file.
enlargeEnlarge diagramA drawn diagram's toolbar: opens the viewer.
showSourceShow sourceThe toolbar: swaps the picture for its source.
showDiagramShow diagramThe same button while the source shows.
copySourceCopy sourceThe toolbar: copies the source. copied after a copy.
downloadSvgDownload SVGThe toolbar: saves diagram.svg.
zoomInZoom inThe diagram viewer.
zoomOutZoom out
fitFit to window
actualSizeActual sizeThe zoom percentage, which shows the diagram at 100%.
closeCloseCloses the viewer.

A callout with a title (> [!TIP] Faster builds) is named by its title instead. DEFAULT_LABELS holds the English set.