Vue AI chat Markdown renderer
markstream-vue is the Vue 3 renderer for AI chat surfaces where Markdown is visible while an LLM response is still streaming. It is useful when the message can contain code fences, tables, Mermaid diagrams, KaTeX math, HTML-like tags, or long reasoning output before the final chunk arrives.
Install
pnpm add markstream-vueimport MarkdownRender from 'markstream-vue'
import 'markstream-vue/index.css'Use index.px.css instead of index.css for mobile WebViews where the host app changes root font scaling.
Minimal Vue chat message
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'
import 'markstream-vue/index.css'
defineProps<{
content: string
isDone: boolean
}>()
</script>
<template>
<MarkdownRender
mode="chat"
:content="content"
:final="isDone"
:fade="false"
/>
</template>The two important props are content and final. Keep appending text to content while the stream is active. Set final to true only when the model has finished, so open fences, tables, math, and heavy blocks can settle into their final rendering.
SSE chat stream example
<script setup lang="ts">
import MarkdownRender from 'markstream-vue'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import 'markstream-vue/index.css'
const content = ref('')
const isDone = ref(false)
let source: EventSource | null = null
onMounted(() => {
source = new EventSource('/api/chat/stream')
source.onmessage = (event) => {
if (event.data === '[DONE]') {
isDone.value = true
source?.close()
return
}
const data = JSON.parse(event.data) as { content?: string }
content.value += data.content ?? ''
}
})
onBeforeUnmount(() => {
source?.close()
})
</script>
<template>
<MarkdownRender
mode="chat"
:content="content"
:final="isDone"
smooth-streaming="auto"
:fade="false"
/>
</template>What this solves in AI chat
| Streaming state | What users should see |
|---|---|
Unclosed ```ts fence | A stable readable code block state, not the rest of the message swallowed unexpectedly |
| Partial table syntax | Text that does not flicker between paragraph and table |
| Partial KaTeX | Source text until the expression is complete enough to render |
| Mermaid diagram still growing | Progressive rendering when the diagram syntax becomes stable |
| Long reasoning answer | Bounded live nodes and viewport-aware heavy blocks when virtualization is enabled |
Safe HTML policy
LLM output is external content. Start with escaped or safe HTML unless your server fully controls the Markdown.
<MarkdownRender
mode="chat"
:content="content"
:final="isDone"
html-policy="escape"
/>Use custom components for trusted tags such as thinking when you want model-specific UI without enabling raw HTML.
When to avoid markstream-vue
Use a smaller static renderer when the content is short, complete before display, and never includes heavy blocks or long streaming states. markstream-vue earns its weight when the user reads the answer while it is still being written.
Next steps
- Rendering raw LLM chunks? Read LLM token stream Markdown.
- Transport is SSE or WebSocket? Read SSE and WebSocket streaming.
- Comparing Vue alternatives? Read markstream-vue vs vue-stream-markdown.
- Handling long answers? Read Long AI responses.