# Markdown Pipelines

# Markdown Pipelines

TanStack Highlight provides helpers at three different Markdown boundaries. All accept an explicit highlighter so adapters do not import every language.

## TanStack Markdown

TanStack Markdown owns the surrounding `<pre><code>` elements. Use the dedicated adapter to return only Highlight's escaped inner token markup:

```ts
import { createTanStackMarkdownHighlighter } from '@tanstack/highlight/markdown'
import { highlighter } from './highlight'

export const highlightMarkdownCode =
  createTanStackMarkdownHighlighter(highlighter)
```

Pass the same callback to the React or HTML renderer:

```tsx
import { Markdown } from '@tanstack/markdown/react'
import { highlightMarkdownCode } from './markdown-highlighter'

export function Article({ source }: { source: string }) {
  return (
    <Markdown highlighter={highlightMarkdownCode} codeLineNumbers>
      {source}
    </Markdown>
  )
}
```

The adapter maps parsed highlighted lines to `th-line--highlighted`, preserves line-number wrappers, escapes source text, and degrades unknown languages to escaped plaintext. It does not import TanStack Markdown or any languages.

Generate theme CSS against Markdown's wrapper classes:

```ts
import { createThemeCss } from '@tanstack/highlight/theme'
import { githubDarkTheme } from '@tanstack/highlight/themes/github-dark'
import { githubLightTheme } from '@tanstack/highlight/themes/github-light'

export const markdownHighlightCss = createThemeCss({
  light: githubLightTheme,
  dark: githubDarkTheme,
  lightSelector: '.markdown-renderer',
  darkSelector: '.dark .markdown-renderer',
  codeBlockSelector: '.markdown-renderer pre.tm-code',
  lineNumbersSelector: '.markdown-renderer .tm-code--line-numbers',
})
```

Wrap the rendered document in `.markdown-renderer`. Add application CSS for `.th-line--highlighted` if highlighted lines need a background.

## Direct fence rendering

```ts
import { renderCodeFence } from '@tanstack/highlight/markdown'
import { highlighter } from './highlight'

const rendered = renderCodeFence(
  {
    code: `const answer = 42\n`,
    lang: 'ts',
    meta: 'title="answer.ts" {1} lineNumbers',
  },
  highlighter,
)
```

The result includes:

```ts
rendered.copyText
rendered.htmlMarkup
rendered.lang
rendered.title
rendered.tokens
rendered.decorations
rendered.lineNumbers
```

`copyText` trims trailing whitespace from the block before highlighting.

## Remark

Use the Remark plugin before `remark-rehype`:

```ts
import { remarkHighlightCodeBlocks } from '@tanstack/highlight/remark'

const highlightCode = remarkHighlightCodeBlocks({
  highlighter,
  lineNumbers: false,
  getTitle(node) {
    return node.data?.filename as string | undefined
  },
  getDecorations(node) {
    return node.lang === 'diff'
      ? [{ lines: 1, className: 'diff-context' }]
      : undefined
  },
})
```

The plugin replaces mdast `code` nodes with a `highlightedCode` extension node carrying `hName`, `hProperties`, and `hChildren`. `remark-rehype` therefore receives structured HAST without enabling raw HTML.

For pipelines that intentionally consume raw HTML, `remarkCodeNodeToHtml()` returns a raw mdast HTML node. Prefer the structured path by default.

## Rehype

Use the Rehype plugin when the tree already contains `<pre><code class="language-*">`:

```ts
import { rehypeHighlightCodeBlocks } from '@tanstack/highlight/rehype'

const highlightCode = rehypeHighlightCodeBlocks({
  highlighter,
  lineNumbers: true,
})
```

The plugin:

1. Finds `<pre>` elements with a `<code>` child.
2. Reads the first `language-*` class from the code element.
3. Collects text from its descendants.
4. Replaces the node with highlighted HAST.

It also reads fence titles, annotations, and line numbers from the code node's `data.meta`, and preserves existing attributes, classes, and plugin data on both elements. An explicit `lineNumbers` option overrides the fence metadata, omit it to let each fence choose. A non-empty `getTitle` result overrides the metadata title.

Already highlighted `th-code` blocks are skipped, making the transform idempotent.

## HAST helpers

```ts
import {
  codeFenceToHast,
  parseCodeFenceMeta,
  tokensToHast,
} from '@tanstack/highlight/markdown'
```

Use these when a custom parser already owns traversal but wants the same output contract.

## Markdown language versus Markdown adapter

They solve different problems:

- `languages/markdown` highlights Markdown source as a code sample and can delegate its internal fences.
- `remark` and `rehype` transform the code fences of a rendered Markdown document.

Most documentation sites need the adapter. Register the Markdown language only when they display Markdown source inside a code block.

`@octanejs/mdx` users can wrap the Rehype adapter in its expected plugin tuple with [`createOctaneMdxHighlight()`](octane#octane-mdx).
