vue3-streamdown

Vue 3 port of streamdown — a drop-in replacement for vue-markdown, designed for AI-powered streaming.
860
Install
npm install vue3-streamdown
Latest Version:0.1.3
Vue:3
License:Apache-2.0
Last Updated:Jun 1, 2026
Links: npm
Maintainer: klmhyeonwoo

vue3-streamdown

A Vue 3 port of streamdown — a drop-in alternative to vue-markdown, built for AI-powered streaming.

npm version npm downloads Vue 3 License TypeScript


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-scheme media query automatically. If you want OS-based toggling, add/remove the .dark class based on window.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-value prop — 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-vue

Streamdown-style streaming Markdown renderer for Vue 3/Nuxt 3 with Shiki, KaTeX,...

333,829 53
@comark/vue

Vue 3 renderer for Comark. Render Markdown with components at runtime, with stre...

1,255,538 1,080
@comark/nuxt

Nuxt module for Comark. Render Markdown with components at runtime, with auto-im...

390,704 1,080
@incremark/vue

High-performance streaming markdown renderer for Vue 3 ecosystem.

10,634 282
@ai-gui/vue

Vue 3 adapter for AIGUI — stream LLM output into live Vue components with cards...

11,789 0

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