Markdown
Renders AI responses written in markdown with Create UI typography, including while the text is still streaming in.
Description
Markdown takes a markdown string and renders it as Create UI prose: headings, paragraphs, lists, tables, quotes, links, images and fenced code blocks with a copy button. It is a single component built on Streamdown, which parses the source into blocks and memoizes each one, so a response that grows token by token only re-renders the block that changed.
Use it for assistant messages in a chat, streamed summaries, release notes pulled from an API, or any content where the source is markdown and you don't control it. Set isStreaming while the response is still arriving: new words fade in, a caret follows the last word, and unfinished syntax such as an unclosed ** or a half-written code fence renders cleanly instead of flashing raw characters.
Don't use it for text you write by hand in JSX. Compose Text Link, Separator and plain elements directly, which skips the parser. For MDX pages with embedded components, use your MDX pipeline. Body text inherits its size and color from the container, so put Markdown inside something that sets them, such as a chat message bubble.
Markdown is a Create UI Pro component. Install it with a Pro seat.
Installation
Usage
Examples
Every example is a Pro preview (marked with a Pro badge).
Typography
Six heading levels, emphasis, strikethrough, inline code, block quotes and horizontal rules. Headings use fixed type tokens (text-heading-h5 down to text-body-xs), while paragraphs follow the size set on the container.
Lists
Ordered, unordered, nested and GFM task lists. Markers sit outside the text, so an item that wraps lines up under its first word. Task lists drop the marker and show a disabled checkbox.
Code blocks
A fenced block renders with a header showing the language (or text when the fence has none), a copy button and a scrollable body capped at 384px. The body is always left to right, even in an RTL layout.
Tables
GFM tables get a tinted header row and 1px cell borders. The table sits inside a wrapper that scrolls sideways, so wide tables never push the chat layout.
Links
http and https links open in a new tab with rel="noopener noreferrer". Relative paths, #anchors, mailto: and tel: stay in the current tab. Anything else, such as javascript: or data:, renders as plain text.
Text size
Markdown sets no font size or text color on body text. Put text-paragraph-xs or text-paragraph-sm on the container (or on Markdown through className) and paragraphs, list items, table cells, links and inline code follow it.
Custom components
components replaces the renderer for any element. Your entries are merged over the defaults, so you only pass what changes. Define the object outside the component: a new object on every render makes every block render again.
Streaming
Pass the accumulated text on every update and keep isStreaming on until the stream closes. Words fade in, the caret sits inline after the last word (never after a code block or table), and the copy button stays disabled until the fence closes. Switching isStreaming off keeps the same tree mounted.
Accessibility
Markdown renders plain semantic HTML, so the reading order and element roles come straight from the source. The only focusable parts are links, copy buttons and code block bodies.
ARIA notes:
- The root sets no role. Headings, lists, tables,
blockquote,hr(asrole="separator") andimgkeep their native semantics. - The copy button is labelled "Copy code" and switches to "Copied" after a successful copy. A visually hidden
role="status"region announces the change. Both strings can be changed withcopyLabelandcopiedLabelonMarkdownCodeBlock. - The caret is CSS generated content with empty alternative text, so screen readers that support it skip the glyph.
- Images keep the alt text from the markdown. An image with no alt text gets
alt=""and is treated as decorative. - Don't put
aria-liveonMarkdownitself: announcing every token is noise. Mark the message list as a log and announce once per finished response. - The word fade is turned off under
prefers-reduced-motion: reduce.
Styling
Tailwind override: className lands on the root. Set the prose size and color there, or restyle parts through descendant selectors.
Data slots and attributes: the component sets these for CSS targeting:
data-slot="markdown"on the root, withdata-streamingwhileisStreamingis on.data-slot="markdown-block"on thedisplay: contentswrapper around each parsed block.data-slot="markdown-h1"todata-slot="markdown-h6",markdown-paragraph,markdown-strong,markdown-blockquote,markdown-ul,markdown-ol,markdown-li,markdown-supandmarkdown-subon the matching elements.data-slot="markdown-inline-code"on inline code spans.data-slot="markdown-link"on links, withdata-externalfor links that open in a new tab anddata-incompletewhile a link is still streaming. Script and data URLs are reduced to plain text before rendering; any other protocol outside the allowlist (for exampleirc:) renders as a<span>withdata-blocked.data-slot="markdown-hr"on the horizontal rule (aSeparator).data-slot="markdown-image"on images.data-slot="markdown-table-wrapper",markdown-table,markdown-thead,markdown-tbody,markdown-tr,markdown-thandmarkdown-tdon table parts.data-slot="markdown-code-block"on the code block frame, withdata-languageanddata-incompletewhile the fence is still open.data-slot="markdown-code-block-header",markdown-code-block-language,markdown-code-block-copy(withdata-copied) andmarkdown-code-block-bodyon its parts.data-sd-animateon each word span that fades in while streaming.
Self-contained CSS: the word fade keyframes, the caret and the reduced-motion guard ship inside the component as a single hoisted <style> tag (React dedupes it across messages). There is no stylesheet to import and no Tailwind @source entry to add for Streamdown.
Related Components
- Chat Message: the bubble and layout around an assistant turn. Put
Markdowninside its content slot. - Text Link: use it directly for links you write in JSX.
Markdownuses it for every link in the source. - Chat Tool: use it for tool call input and output instead of formatting JSON as a markdown code block.
API Reference
Markdown
Parses a markdown string and renders it with Create UI components. Extends React.ComponentProps<"div"> (except children), so any standard div attribute (id, dir, aria-*, etc.) is passed to the root.
Props
MarkdownCodeBlock
The fenced code block frame: language label, copy button and scrollable <pre>. Markdown renders it for every fence. Use it directly when you override code in components and want to keep the frame. Extends React.ComponentProps<"div"> (except children).
Props
markdownComponents
The default renderer map Markdown passes to Streamdown, typed as Components. Spread it when you build your own renderer on top of Streamdown, or read an entry to wrap a default.
Keys: a, blockquote, code, h1 to h6, hr, img, inlineCode, li, ol, p, strong, sub, sup, table, tbody, td, th, thead, tr, ul.
Types
Components is re-exported from Streamdown: a map from an HTML tag name (plus inlineCode) to a React component that receives that element's props and a node prop with the parsed hast element. Drop node before spreading props onto the DOM.