vue3-streamdown
| Install | |
|---|---|
npm install vue3-streamdown |
|
| Latest Version: | 0.1.3 |
| Vue: | 3 |
| License: | Apache-2.0 |
| Last Updated: | Jun 1, 2026 |
| Links: | npm |
vue3-streamdown
A Vue 3 port of streamdown — a drop-in alternative to vue-markdown, built for AI-powered streaming.
Overview
Streaming Markdown from LLMs creates unique rendering challenges: incomplete code fences, partial tables, and unterminated blocks can all break a naive renderer. vue3-streamdown handles all of this gracefully — the same battle-tested approach as Vercel's streamdown, now for the Vue ecosystem.
Works with any AI streaming API: Vercel AI SDK, OpenAI, Anthropic, or raw SSE streams.
Features
- ⚡ Streaming-optimized — gracefully handles partial/incomplete Markdown from LLMs
- 🎨 Syntax highlighting — beautiful code blocks via Shiki
- 📊 GitHub Flavored Markdown — tables, task lists, strikethrough
- 📈 Mermaid diagrams — render flowcharts, sequence diagrams, and more
- 🔢 Math rendering — LaTeX equations via KaTeX
- 🌐 CJK support — proper word-wrapping for Chinese/Japanese/Korean
- 🛡️ Security-first — built-in HTML sanitization via
rehype-sanitize+rehype-harden - 🎯 Animated cursor — blinking block/circle caret during streaming
- 🔗 Link safety — external link confirmation modal
- 🌙 Dark mode — class-based dark mode (
.dark), compatible with shadcn/ui - 💪 Fully typed — complete TypeScript support with Composition API
Preview
Streaming with animated caret
┌─────────────────────────────────────────────────────┐
│ │
│ Here is a Fibonacci sequence in Python: │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ python [⎘] [↓] │ │
│ │ 1 def fib(n): │ │
│ │ 2 if n <= 1: │ │
│ │ 3 return n │ │
│ │ 4 return fib(n-1) + fib(n-2) ▋ │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
Table with copy/download controls
┌──────────────────────────────────────────────────────────┐
│ Framework │ Stars │ License [⎘] [↓] [⛶] │
│──────────────┼─────────┼──────────────────────────────── │
│ Vue 3 │ 48k │ MIT │
│ React │ 226k │ MIT │
│ Svelte │ 80k │ MIT │
└──────────────────────────────────────────────────────────┘
Mermaid diagram rendering
┌────────────────────────────────────────────────────────────┐
│ mermaid [↓▾] [⎘] [⛶] [🔍±] │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Client ──► API Server ──► Database │ │
│ │ ▲ │ │ │
│ │ └──────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Installation
npm install vue3-streamdown
# or
pnpm add vue3-streamdown
# or
yarn add vue3-streamdown
Styles are automatically injected when the library is imported — no additional CSS import or Tailwind setup required.
If you prefer to manage styles manually (e.g. for SSR or CSP reasons), you can import the pre-compiled CSS directly instead:
import "vue3-streamdown/style.css";
CSS Custom Properties (shadcn/ui design tokens)
Components use shadcn/ui CSS variables for theming. If you already use shadcn/ui these are set automatically. Otherwise, add the following minimal set to your global CSS:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--border: oklch(0.922 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--sidebar: oklch(0.985 0 0);
--radius: 0.625rem;
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--border: oklch(0.269 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--sidebar: oklch(0.205 0 0);
--radius: 0.625rem;
}
Dark mode
vue3-streamdown uses class-based dark mode: dark styles are applied when a .dark class is present on any ancestor element (typically <html>). This matches shadcn/ui's default behavior.
// Toggle dark mode
document.documentElement.classList.toggle("dark");
Dark mode does not follow the OS
prefers-color-schememedia query automatically. If you want OS-based toggling, add/remove the.darkclass based onwindow.matchMedia('(prefers-color-scheme: dark)')yourself.
Usage
Basic usage
<script setup lang="ts">
import { Streamdown } from "vue3-streamdown";
</script>
<template>
<Streamdown :model-value="markdownText" />
</template>
Note: Unlike the React version, content is passed via the
model-valueprop — not as slot content.
Streaming with Vercel AI SDK
<script setup lang="ts">
import { useChat } from "@ai-sdk/vue";
import { Streamdown } from "vue3-streamdown";
const { messages, status } = useChat();
</script>
<template>
<div v-for="message in messages" :key="message.id">
<template v-for="part in message.parts" :key="part.type">
<Streamdown
v-if="part.type === 'text'"
:model-value="part.text"
:is-animating="status === 'streaming'"
animated
caret="block"
/>
</template>
</div>
</template>
Streaming with raw fetch / SSE
<script setup lang="ts">
import { ref } from "vue";
import { Streamdown } from "vue3-streamdown";
const content = ref("");
const isStreaming = ref(false);
async function chat(prompt: string) {
isStreaming.value = true;
content.value = "";
const response = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ prompt }),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
content.value += decoder.decode(value);
}
isStreaming.value = false;
}
</script>
<template>
<button @click="chat('Explain quicksort with code')">Ask AI</button>
<Streamdown
:model-value="content"
:is-animating="isStreaming"
mode="streaming"
caret="block"
animated
/>
</template>
With syntax highlighting (Shiki)
Install the code plugin:
npm install @streamdown/code
<script setup lang="ts">
import { Streamdown } from "vue3-streamdown";
import { createCodePlugin } from "@streamdown/code";
const codePlugin = createCodePlugin();
</script>
<template>
<Streamdown :model-value="markdown" :plugins="{ code: codePlugin }" />
</template>
With Mermaid diagrams
Install the mermaid plugin:
npm install @streamdown/mermaid
<script setup lang="ts">
import { Streamdown } from "vue3-streamdown";
import { createMermaidPlugin } from "@streamdown/mermaid";
const mermaidPlugin = createMermaidPlugin();
</script>
<template>
<Streamdown :model-value="markdown" :plugins="{ mermaid: mermaidPlugin }" />
</template>
With math (KaTeX)
Install the math plugin and KaTeX CSS:
npm install @streamdown/math katex
<script setup lang="ts">
import { Streamdown } from "vue3-streamdown";
import { createMathPlugin } from "@streamdown/math";
import "katex/dist/katex.min.css";
const mathPlugin = createMathPlugin();
</script>
<template>
<Streamdown :model-value="markdown" :plugins="{ math: mathPlugin }" />
</template>
All plugins together
<script setup lang="ts">
import { Streamdown } from "vue3-streamdown";
import { createCodePlugin } from "@streamdown/code";
import { createMermaidPlugin } from "@streamdown/mermaid";
import { createMathPlugin } from "@streamdown/math";
import "katex/dist/katex.min.css";
const plugins = {
code: createCodePlugin(),
mermaid: createMermaidPlugin(),
math: createMathPlugin(),
};
</script>
<template>
<Streamdown
:model-value="content"
:is-animating="isStreaming"
:plugins="plugins"
mode="streaming"
caret="block"
animated
/>
</template>
Custom translations (i18n)
<template>
<Streamdown
:model-value="markdown"
:translations="{
copyCode: '코드 복사',
copied: '복사됨',
viewFullscreen: '전체 화면',
exitFullscreen: '닫기',
downloadFile: '파일 다운로드',
}"
/>
</template>
Tailwind CSS prefix support
<template>
<!-- All internal Tailwind classes will be prefixed: tw:flex, tw:text-sm, etc. -->
<Streamdown :model-value="markdown" prefix="tw" />
</template>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
model-value |
string |
"" |
Markdown content to render |
mode |
"streaming" | "static" |
"streaming" |
Rendering mode |
is-animating |
boolean |
false |
Whether the stream is active |
animated |
boolean | AnimateOptions |
— | Enable token-by-token animation |
caret |
"block" | "circle" |
— | Blinking cursor style during streaming |
controls |
boolean | ControlsConfig |
true |
Show code/table/mermaid action buttons |
line-numbers |
boolean |
true |
Show line numbers in code blocks |
shiki-theme |
[ThemeInput, ThemeInput] |
["github-light", "github-dark"] |
Shiki [lightTheme, darkTheme] — first theme for light mode, second for dark mode (.dark class) |
plugins |
PluginConfig |
— | code / mermaid / math / cjk plugins |
remark-plugins |
Pluggable[] |
— | Custom remark plugins |
rehype-plugins |
Pluggable[] |
— | Custom rehype plugins |
components |
Components |
— | Override hast → Vue component map |
translations |
Partial<StreamdownTranslations> |
— | Override UI strings |
icons |
Partial<IconMap> |
— | Override toolbar icons |
prefix |
string |
— | Tailwind CSS class prefix |
link-safety |
LinkSafetyConfig |
{ enabled: true } |
External link confirmation |
mermaid |
MermaidOptions |
— | Mermaid global config |
dir |
"auto" | "ltr" | "rtl" |
— | Text direction (auto = per-block detection) |
parse-incomplete-markdown |
boolean |
true |
Use remend for streaming-safe parsing |
normalize-html-indentation |
boolean |
false |
Prevent indented HTML being treated as code |
allowed-tags |
Record<string, string[]> |
— | Custom HTML tags to allow through sanitization |
literal-tag-content |
string[] |
— | Tags whose children are treated as plain text |
remend |
RemendOptions |
— | Options passed to remend |
on-animation-start |
() => void |
— | Called when streaming starts |
on-animation-end |
() => void |
— | Called when streaming ends |
Composables (for custom child components)
If you build custom components that need to access the streamdown context:
import {
useStreamdownContext, // shikiTheme, controls, isAnimating, etc.
useTranslations, // UI strings
useIcons, // toolbar icons
useCn, // prefix-aware cn()
usePlugins, // full plugin config
useCodePlugin, // code plugin
useMermaidPlugin, // mermaid plugin
useIsBlockIncomplete, // whether current block is mid-stream
useIsBlockCode, // whether <code> is inside a <pre>
useCodeBlock, // { code: ComputedRef<string> } for copy/download
useAnimate, // animate plugin instance
} from "vue3-streamdown";
Comparison with streamdown (React)
| Feature | streamdown (React) |
vue3-streamdown (Vue 3) |
|---|---|---|
| Streaming-safe parsing | ✅ | ✅ |
| Syntax highlighting (Shiki) | ✅ | ✅ |
| Mermaid diagrams | ✅ | ✅ |
| Math (KaTeX) | ✅ | ✅ |
| CJK support | ✅ | ✅ |
| Animated cursor | ✅ | ✅ |
| Link safety modal | ✅ | ✅ |
| Dark mode | ✅ | ✅ |
| Tailwind prefix | ✅ | ✅ |
| TypeScript | ✅ | ✅ |
| Composition API / hooks | React hooks | Vue composables |
| SSR | ✅ (Next.js) | ✅ (Nuxt) |
License
Apache-2.0 — based on streamdown by Vercel.
Related Packages
Streamdown-style streaming Markdown renderer for Vue 3/Nuxt 3 with Shiki, KaTeX,...
Vue 3 renderer for Comark. Render Markdown with components at runtime, with stre...
Nuxt module for Comark. Render Markdown with components at runtime, with auto-im...
Vue 3 adapter for AIGUI — stream LLM output into live Vue components with cards...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.1.3 | ^3.3.0 | Apache-2.0 | |
| 0.1.2 | ^3.3.0 | Apache-2.0 | |
| 0.1.1 | ^3.3.0 | Apache-2.0 | |
| 0.1.0 | ^3.3.0 | Apache-2.0 |