Marble UI
← Components

Markdown Response

Preview: Rendered

Renders model output as Markdown (GFM tables, task lists, code). Safe by default: raw HTML in the text is not rendered, so model output can't inject markup.

Import

import { MarkdownResponse } from '@marbleui/ui'

Props

<MarkdownResponse>

PropTypeDefaultDescription
children *requiredstring—
classNamestring—
isStreamingboolean—Show a blinking caret at the end while tokens stream in.
size'sm' | 'md''sm'

Usage notes

Renders model output as Markdown: GFM tables, task lists, links and fenced code. Use it as the body of an assistant ChatMessage. For trusted content you render yourself, wrap it in Prose.

<ChatMessage role="assistant" name="Acme AI" actions={<MessageActions text={reply} />}>
  <MarkdownResponse isStreaming={status === 'streaming'}>{reply}</MarkdownResponse>
</ChatMessage>
  • children must be a string. Pass the whole text so far on each token; isStreaming adds a blinking caret after the last block. size is sm (default) or md.
  • Raw HTML in the text shows as literal text, so model output can’t inject markup. Links open in a new tab.
  • Fenced code becomes a CodeBlock with a copy button, and line numbers on longer blocks. Task-list checkboxes are read-only and named “Done” or “Not done”.
  • Because it only takes a string, Citations can’t go inside it; list sources after it with Sources.
Example source · show
import { useEffect, useState } from 'react'
import { Button } from '../button'
import { MarkdownResponse } from './markdown-response'

const answer = `Here's how to theme **Marble UI** per section:

1. Wrap the area in an element with \`data-theme="soft"\`
2. Add the \`dark\` class for dark mode
3. Overlays inherit it via the portal container

| Preset | Radius | Accent |
| --- | --- | --- |
| Minimal | 8px | Ink |
| Soft | 14px | Violet |

\`\`\`tsx
<div data-theme="soft" className="dark">
  <Button>Themed</Button>
</div>
\`\`\`

> Tip: chart colors stay the same across presets on purpose.

- [x] Tokens
- [ ] Your brand preset`

function Streaming() {
  const [n, setN] = useState(0)
  useEffect(() => {
    if (n >= answer.length) return
    const t = setTimeout(() => setN((v) => Math.min(answer.length, v + 6)), 30)
    return () => clearTimeout(t)
  }, [n])
  return (
    <div className="flex w-[34rem] flex-col gap-3">
      <MarkdownResponse isStreaming={n < answer.length}>{answer.slice(0, n)}</MarkdownResponse>
      <Button size="sm" variant="outline" className="self-start" onPress={() => setN(0)}>Replay</Button>
    </div>
  )
}

export const styles = {
  Rendered: () => <div className="w-[34rem]"><MarkdownResponse>{answer}</MarkdownResponse></div>,
  Streaming,
}

Source: packages/ui/src/markdown-response/markdown-response.tsx

Catalog

Category
ai-agents
Priority
Pro
Primitive
custom