tanstack/markdown · Archived

react-rendering

Render Markdown source or a MarkdownDocument with @tanstack/markdown/react using Markdown, renderMarkdownReact, component replacements, and React static SSR. Load for React article components, emitted-tag mappings, pre-parsed documents, custom elements, or renderer parity.

First seen Aug 23, 2026

Installation

$ npx skills add tanstack/markdown --skill react-rendering

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from tanstack/markdown.

npx skills add tanstack/markdown

Browse all from tanstack/markdown

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 126
Default branch main
Open issues 4
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

More metadata
type
framework
library
@tanstack/markdown
framework
react
library_version
0.0.13

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,825 B
  • docs SUMMARY.md 296 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 3 installs

SKILL.md

This skill builds on [render-markdown](../render-markdown/SKILL.md). Read it first for the syntax profile, parser options, AST, and core trust boundaries.

React Rendering

Setup

Render source directly and keep the surrounding semantic element in the application:

import { Markdown } from '@tanstack/markdown/react'

export function Article({ source }: { source: string }) {
  return (
    <article>
      <Markdown>{source}</Markdown>
    </article>
  )
}

Markdown returns a React fragment. The React adapter requires React 18 or newer.

Hooks and Components

Replace emitted links with an application component

import {
  Markdown,
  type MarkdownComponents,
} from '@tanstack/markdown/react'

const components = {
  a(props) {
    const external = props.href?.startsWith('http') ?? false
    return (
      <a
        {...props}
        rel={external ? 'nofollow noopener noreferrer' : props.rel}
        target={external ? '_blank' : props.target}
      />
    )
  },
} satisfies MarkdownComponents

export function Article({ source }: { source: string }) {
  return (
    <article>
      <Markdown components={components}>{source}</Markdown>
    </article>
  )
}

Known intrinsic keys infer their matching React props. Arbitrary extension component tag names remain supported.

Parse once before repeated React rendering

import { parseMarkdown } from '@tanstack/markdown/parser'
import { Markdown } from '@tanstack/markdown/react'

const document = parseMarkdown('# Cached article\n\nRendered from an AST.')

export function Article() {
  return (
    <article>
      <Markdown>{document}</Markdown>
    </article>
  )
}

Apply parser options and document transforms when creating the document; renderer options cannot retroactively change its structure.

Render static React markup on the server

import { renderToStaticMarkup } from 'react-dom/server'
import { Markdown } from '@tanstack/markdown/react'

const source = '# Server-rendered article'

export const html = renderToStaticMarkup(
  <article>
    <Markdown>{source}</Markdown>
  </article>,
)

Core static output is tested for structural equivalence with renderHtml().

Compose through the lower-level node API

import type { ReactNode } from 'react'
import { parseMarkdown } from '@tanstack/markdown/parser'
import { renderMarkdownReact } from '@tanstack/markdown/react'

const document = parseMarkdown('# Parsed once')

export function ArticleBody(): ReactNode {
  return renderMarkdownReact(document)
}

Use renderBlockReact or renderInlineReact only when custom tree composition needs individual public AST nodes.

Common Mistakes

HIGH Injecting HTML instead of React nodes

Wrong:

import { renderHtml } from '@tanstack/markdown'

export function Article({ source }: { source: string }) {
  const html = renderHtml(source)
  return <article dangerouslySetInnerHTML={{ __html: html }} />
}

Correct:

import { Markdown } from '@tanstack/markdown/react'

export function Article({ source }: { source: string }) {
  return (
    <article>
      <Markdown>{source}</Markdown>
    </article>
  )
}

The HTML-string path bypasses React component replacement and creates an unnecessary trusted-HTML boundary.

Source: docs/guides/react.md

HIGH Mapping AST node names instead of tags

Wrong:

import type { ComponentProps } from 'react'
import { Markdown } from '@tanstack/markdown/react'

function ArticleLink(props: ComponentProps<'a'>) {
  return <a {...props} data-navigation="article" />
}

export function Article() {
  return (
    <Markdown components={{ link: ArticleLink }}>
      {'Read the [guide](/guide).'}
    </Markdown>
  )
}

Correct:

import type { ComponentProps } from 'react'
import { Markdown } from '@tanstack/markdown/react'

function ArticleLink(props: ComponentProps<'a'>) {
  return <a {...props} data-navigation="article" />
}

export function Article() {
  return (
    <Markdown components={{ a: ArticleLink }}>
      {'Read the [guide](/guide).'}
    </Markdown>
  )
}

The map is keyed by emitted tag names such as a, not AST discriminants such as link.

Source: docs/guides/react.md

HIGH Expecting HTML extension hooks in React

Wrong:

import type { MarkdownExtension } from '@tanstack/markdown'
import { Markdown } from '@tanstack/markdown/react'

const paragraphHtml: MarkdownExtension = {
  name: 'paragraph-html',
  renderHtml(node) {
    return node.type === 'paragraph'
      ? '<aside>Rendered only by the HTML renderer</aside>'
      : undefined
  },
}

export function Article() {
  return <Markdown extensions={[paragraphHtml]}>Ordinary content</Markdown>
}

Correct:

import type { MarkdownExtension } from '@tanstack/markdown'
import type { PropsWithChildren } from 'react'
import { Markdown } from '@tanstack/markdown/react'

const panelExtension: MarkdownExtension = {
  name: 'panel',
  transformDocument(document) {
    return {
      ...document,
      children: [
        {
          type: 'component',
          name: 'panel',
          tagName: 'doc-panel',
          attributes: {},
          children: document.children,
        },
      ],
    }
  },
}

function Panel({ children }: PropsWithChildren) {
  return <aside className="documentation-panel">{children}</aside>
}

export function Article() {
  return (
    <Markdown
      components={{ 'doc-panel': Panel }}
      extensions={[panelExtension]}
    >
      Ordinary content
    </Markdown>
  )
}

MarkdownExtension.renderHtml is HTML-specific; portable custom output uses ComponentNode and an emitted-tag component mapping.

Source: docs/guides/extensions.md

CRITICAL Enabling raw HTML for untrusted input

Wrong:

import { Markdown } from '@tanstack/markdown/react'

export function UserPost({ source }: { source: string }) {
  return <Markdown allowHtml>{source}</Markdown>
}

Correct:

import { Markdown } from '@tanstack/markdown/react'

export function UserPost({ source }: { source: string }) {
  return <Markdown>{source}</Markdown>
}

With allowHtml, React uses dangerouslySetInnerHTML; TanStack Markdown does not sanitize that raw HTML.

Source: docs/core-concepts/security.md

HIGH Tension: renderer parity versus React customization

Core React SSR is tested against renderHtml(). React component replacements, raw HTML, highlighter output, and HTML-only extension hooks are application-controlled boundaries outside that parity guarantee.

See also: [custom-extensions](../custom-extensions/SKILL.md) for portable ComponentNode output.

Cross-References

  • [render-markdown](../render-markdown/SKILL.md) - shared MarkdownInput, parser options, AST behavior, and syntax profile.
  • [custom-extensions](../custom-extensions/SKILL.md) - emit custom tags that React can replace through components.
  • [production-pipelines](../production-pipelines/SKILL.md) - audit raw HTML, highlighter output, untrusted content, and SSR boundaries.