npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue3-streamdown

v0.1.3

Published

Vue 3 port of streamdown — a drop-in replacement for vue-markdown, designed for AI-powered streaming.

Downloads

162

Readme

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.