TanStack
Migration

TextPart markdown

TL;DR: This is a breaking change. TextPart no longer accepts remarkPlugins, rehypePlugins, or disableDefaultPlugins. Pass extensions and highlighter instead. Default <TextPart content={...} /> still type-checks. Bare URLs stay plain text. Raw HTML in the model is escaped.

TextPart from @tanstack/ai-react/ui, @tanstack/ai-vue/ui, and @tanstack/ai-solid/ui now renders with TanStack Markdown.

This page is only for people who already import that component. createChatHook does not use it unless you pass it as partsComponents.text. Deprecated ChatMessage still prints plain text.

Who this hits

  • You passed remarkPlugins, rehypePlugins, or disableDefaultPlugins. Type-check fails.
  • You passed Solid components. Type-check fails. React components stays, with a new type.
  • You used default TextPart with only content. It compiles. Output can look different (see Silent changes).

Prop map

Old propNew prop
remarkPluginsextensions (MarkdownExtension[] from @tanstack/markdown)
rehypePluginsextensions or highlighter
disableDefaultPluginsgone. The streaming extension is always on
Solid componentsgone
React componentscomponents (MarkdownComponents: a tag-name map with normal HTML props)

A unified plugin such as remark-gfm or remark-cjk-friendly is not a TanStack Markdown extension. You cannot pass it through.

Replace plugin props

CJK bold

TanStack Markdown parses CJK bold with no plugin.

tsx
import { TextPart } from '@tanstack/ai-react/ui'
import remarkCjkFriendly from 'remark-cjk-friendly'

<TextPart content={content} remarkPlugins={[remarkCjkFriendly]} />
tsx
import { TextPart } from '@tanstack/ai-react/ui'

export function Reply({ content }: { content: string }) {
  return <TextPart content={content} />
}

Syntax highlighting

The old stack ran rehype-highlight, then rehype-sanitize stripped the hljs-* classes. Fenced code already looked plain.

To get colors now, pass a TanStack Highlight callback. Full steps: Highlight markdown code.

tsx
<TextPart content={content} />
tsx
import { TextPart } from '@tanstack/ai-react/ui'
import { highlightMarkdownCode } from './markdown-highlighter'

export function Reply({ content }: { content: string }) {
  return (
    <TextPart
      content={content}
      highlighter={highlightMarkdownCode}
      className="markdown-renderer"
    />
  )
}

Extra remark or rehype plugins

There is no drop-in for a unified plugin. Rewrite the behavior as a TanStack Markdown extension, or handle it outside TextPart.

disableDefaultPlugins is gone. You cannot turn off the streaming extension.

React components

React still accepts components. The type is no longer react-markdown Components. TanStack Markdown passes intrinsic HTML props only.

A common override still type-checks, then mis-renders. inline is always undefined.

tsx
<TextPart
  content={content}
  components={{
    code: ({ inline, children }) =>
      inline ? <code>{children}</code> : <pre>{children}</pre>,
  }}
/>
tsx
import { TextPart } from '@tanstack/ai-react/ui'

export function Reply({ content }: { content: string }) {
  return (
    <TextPart
      content={content}
      components={{
        a: (props) => <a {...props} target="_blank" rel="noreferrer" />,
      }}
    />
  )
}

Branch on className (for example language-ts) if you need fence vs inline. Do not read inline or node.

Silent changes

These happen with default <TextPart content={...} />. There is no type error.

InputBeforeAfter
Bare URL https://example.comAuto-linkedPlain text
Raw HTML such as <br> or <img>Sanitized, then renderedEscaped text
Task listcontains-task-list / task-list-itemCheckbox on a plain <li>
Fenced codePlain (highlight classes stripped)Plain, with tm-code / language-*
CJK bold **…。**次Failed without a pluginWorks

Tables, lists, strikethrough, and fenced code still render.

What does not change

  • createChatHook apps that render {part.content} themselves.
  • Deprecated ChatMessage text output (plain text).
  • Import path: still @tanstack/ai-react/ui (or Vue / Solid /ui).
  • The TextPart data type on messages ({ type: 'text', content }). That is not this component.

When the type error is gone and you want colored fences, follow Highlight markdown code.