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.
| Old prop | New prop |
|---|---|
| remarkPlugins | extensions (MarkdownExtension[] from @tanstack/markdown) |
| rehypePlugins | extensions or highlighter |
| disableDefaultPlugins | gone. The streaming extension is always on |
| Solid components | gone |
| React components | components (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.
TanStack Markdown parses CJK bold with no plugin.
import { TextPart } from '@tanstack/ai-react/ui'
import remarkCjkFriendly from 'remark-cjk-friendly'
<TextPart content={content} remarkPlugins={[remarkCjkFriendly]} />import { TextPart } from '@tanstack/ai-react/ui'
export function Reply({ content }: { content: string }) {
return <TextPart content={content} />
}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.
<TextPart content={content} />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"
/>
)
}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 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.
<TextPart
content={content}
components={{
code: ({ inline, children }) =>
inline ? <code>{children}</code> : <pre>{children}</pre>,
}}
/>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.
These happen with default <TextPart content={...} />. There is no type error.
| Input | Before | After |
|---|---|---|
| Bare URL https://example.com | Auto-linked | Plain text |
| Raw HTML such as <br> or <img> | Sanitized, then rendered | Escaped text |
| Task list | contains-task-list / task-list-item | Checkbox on a plain <li> |
| Fenced code | Plain (highlight classes stripped) | Plain, with tm-code / language-* |
| CJK bold **…。**次 | Failed without a plugin | Works |
Tables, lists, strikethrough, and fenced code still render.
When the type error is gone and you want colored fences, follow Highlight markdown code.