https://github.com/saluana/streamdown-vue
Streamdown style streaming Markdown to Vue 3 & Nuxt 3
https://github.com/saluana/streamdown-vue
ai llm markdown markdown-stream streamdown vercel
Last synced: 3 months ago
JSON representation
Streamdown style streaming Markdown to Vue 3 & Nuxt 3
- Host: GitHub
- URL: https://github.com/saluana/streamdown-vue
- Owner: Saluana
- License: mit
- Created: 2025-09-02T16:54:07.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2025-09-03T01:53:10.000Z (10 months ago)
- Last Synced: 2025-09-03T03:34:10.399Z (10 months ago)
- Topics: ai, llm, markdown, markdown-stream, streamdown, vercel
- Language: TypeScript
- Homepage:
- Size: 282 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# streamdown-vue
`streamdown-vue` brings [Streamdown](https://github.com/vercel/streamdown)-style streaming Markdown to Vue 3 & Nuxt 3. It ships a `` component that incrementally renders Markdown as it arrives (token‑by‑token, chunk‑by‑chunk), plus helper utilities to keep partially received text valid.
---
## Table of Contents
1. Features
2. Installation
3. Quick Start (Basic SSR + CSR)
4. Default Styling & Customization
5. Deep Dive Tutorial (Streaming from an AI / SSE source)
6. Props Reference (All `` props)
7. Component Slots & Overrides
8. Built‑in Components & Data Attributes
9. Security Model (Link/Image hardening)
10. Syntax Highlighting (Shiki), Copy / Download & Extensible Actions
11. Mermaid Diagrams
12. Math & LaTeX Fixes
13. Utilities (`parseBlocks`, `parseIncompleteMarkdown`, LaTeX helpers)
14. Performance Tips
15. Nuxt 3 Usage & SSR Notes
16. Recipe Gallery
17. FAQ
18. Development & Contributing
---
## 1. Features
- GitHub‑flavored Markdown (tables, task lists, strikethrough) via `remark-gfm`
- KaTeX math (`remark-math` + `rehype-katex`) with extra repairs (matrices, stray `$`)
- Shiki syntax highlighting (light + dark themes) with reactive copy & download buttons
and an extensible action bar (add your own buttons globally or per-instance)
- Mermaid diagrams with caching, async render & graceful error recovery
- Incremental rendering + repair of incomplete Markdown tokens while streaming
- Secure allow‑list based hardening of link & image URLs (blocks `javascript:` etc.)
- Component override layer (swap any tag / embed custom Vue components)
- Data attributes for each semantic element (`data-streamdown="..."`) for styling/testing
- Designed for SSR (Vue / Nuxt) & fast hydration; tree‑shakable, side‑effects minimized
---
## 2. Installation
### Bun
```bash
bun add streamdown-vue
```
### npm / pnpm / yarn
```bash
npm install streamdown-vue
# pnpm add streamdown-vue
# yarn add streamdown-vue
```
You must also install peer deps `vue` (and optionally `typescript`).
Include KaTeX stylesheet once (if you use math):
```ts
import 'katex/dist/katex.min.css';
```
---
## 3. Quick Start
`main.ts`:
```ts
import { createApp } from 'vue';
import App from './App.vue';
import 'katex/dist/katex.min.css';
createApp(App).mount('#app');
```
`App.vue`:
```vue
import { StreamMarkdown } from 'streamdown-vue';
const markdown = `# Hello\n\nSome *markdown* with $e^{i\\pi}+1=0$.`;
```
SSR (server) minimal snippet:
```ts
import { renderToString } from '@vue/server-renderer';
import { createSSRApp, h } from 'vue';
import { StreamMarkdown } from 'streamdown-vue';
const app = createSSRApp({
render: () => h(StreamMarkdown, { content: '# SSR' }),
});
const html = await renderToString(app);
```
---
## 4. Default Styling & Customization
### Importing the Default Stylesheet
`streamdown-vue` ships with an optional built-in stylesheet that provides clean, neutral, and compact styling for all markdown elements. To use it:
```ts
import 'streamdown-vue/style.css';
```
**What's included:**
- Neutral color scheme with automatic dark mode support
- Compact, professional styling for tables and code blocks
- Subtle, transparent scrollbars
- Properly styled buttons, headings, lists, and blockquotes
- Line number styling (when enabled)
- All styles are scoped to `.streamdown-vue` to avoid conflicts
**Example:**
```ts
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import 'streamdown-vue/style.css'; // ← Import default styles
import 'katex/dist/katex.min.css';
createApp(App).mount('#app');
```
### Customizing with CSS Variables
All styles use CSS variables prefixed with `--sd-*` that you can override. See the [full list of variables in style.css](./src/style.css).
```css
:root {
/* Colors */
--sd-primary: #3b82f6; /* Accent color for links, headings */
--sd-primary-variant: #2563eb; /* Hover states */
--sd-on-surface: #1f2937; /* Text color */
--sd-surface-container: #f3f4f6; /* Code block backgrounds */
--sd-border-color: #e5e7eb; /* Borders */
/* Typography */
--sd-font-family-base: system-ui, sans-serif;
--sd-font-family-mono: ui-monospace, monospace;
--sd-font-size-base: 16px;
--sd-line-height-base: 1.7;
/* Dimensions */
--sd-border-width: 1px;
--sd-border-radius: 0.375rem;
}
```
**Example - Custom Brand Colors:**
```css
:root {
--sd-primary: #8b5cf6; /* Purple accent */
--sd-primary-variant: #7c3aed;
}
```
### Dark Mode
Dark mode is automatic via `@media (prefers-color-scheme: dark)` or by adding the `.dark` class to your `` element:
```ts
// Toggle dark mode
const toggleDark = () => {
document.documentElement.classList.toggle('dark');
};
```
You can customize dark mode colors:
```css
:root.dark {
--sd-primary: #60a5fa; /* Lighter blue for dark mode */
--sd-on-surface: #f3f4f6; /* Light text */
--sd-surface-container: #374151; /* Dark gray backgrounds */
--sd-border-color: #374151;
}
```
### Completely Custom Styling
If you prefer to write your own styles from scratch, simply **don't import** `streamdown-vue/style.css`. All markdown elements have `data-streamdown` attributes for easy targeting:
```css
/* Your custom styles */
.streamdown-vue [data-streamdown='code-block'] {
/* Custom code block styling */
}
.streamdown-vue [data-streamdown='table'] {
/* Custom table styling */
}
```
See section 9 (Built-in Components & Data Attributes) for a complete list of available attributes.
---
## 5. Deep Dive Tutorial – Live Streaming (AI / SSE)
When receiving tokens / partial chunks you typically want to:
1. Append new text chunk into a buffer.
2. Repair the partial Markdown (`parseIncompleteMarkdown`).
3. Split into safe blocks for re-render (`parseBlocks`).
4. Feed the concatenated repaired text to ``.
Composable example (client side):
```ts
// useStreamedMarkdown.ts
import { ref } from 'vue';
import { parseBlocks, parseIncompleteMarkdown } from 'streamdown-vue';
export function useStreamedMarkdown() {
const rawBuffer = ref('');
const rendered = ref('');
const blocks = ref([]);
const pushChunk = (text: string) => {
rawBuffer.value += text;
// repair incomplete tokens (unclosed **, `, $$, etc.)
const repaired = parseIncompleteMarkdown(rawBuffer.value);
blocks.value = parseBlocks(repaired);
rendered.value = blocks.value.join('');
};
return { rawBuffer, rendered, blocks, pushChunk };
}
```
Using Server-Sent Events (SSE):
```ts
const { rendered, pushChunk } = useStreamedMarkdown();
const es = new EventSource('/api/chat');
es.onmessage = (e) => {
pushChunk(e.data);
};
es.onerror = () => es.close();
```
Template:
```vue
```
Why repair first? Without repair, a trailing `**` or lone ``` will invalidate the final tree and cause flicker or lost highlighting. Repairing keeps intermediate renders stable.
---
## 6. Props Reference
| Prop | Type | Default | Description |
| -------------------------- | -------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `content` | `string` | `''` | The full (or partially streamed) markdown source. |
| `class` / `className` | `string` | `''` | Optional wrapper classes; both accepted (React-style alias). |
| `components` | `Record` | `{}` | Map to override built-ins (e.g. `{ p: MyP }`). |
| `remarkPlugins` | `any[]` | `[]` | Extra remark plugins. Supports `(plugin)` or `[plugin, options]`. If you supply `remark-math` yourself, the built‑in one (which disables single‑dollar inline math) is skipped. |
| `rehypePlugins` | `any[]` | `[]` | Extra rehype plugins. |
| `defaultOrigin` | `string?` | `undefined` | Base URL used to resolve relative links/images before allow‑list checks. |
| `allowedImagePrefixes` | `string[]` | `['https://','http://']` | Allowed (lowercased) URL prefixes for `
`. Blocked => image dropped. |
| `allowedLinkPrefixes` | `string[]` | `['https://','http://']` | Allowed prefixes for ``. Blocked => link text only. |
| `parseIncompleteMarkdown` | `boolean` | `true` | (Future toggle) Auto apply repair internally. Currently you repair outside using utility; prop reserved. |
| `shikiTheme` | `ShikiThemeConfig` | `undefined` | Shiki theme (string) or dual theme object `{ light: '...', dark: '...' }`. If undefined, follows system preference (`github-light`/`github-dark`). |
| `codeBlockActions` | `Component[]` | `[]` | Array of Vue components appended as action buttons in every code block header. |
| `codeBlockShowLineNumbers` | `boolean` | `false` | Show line numbers in all code fences. |
| `codeBlockSelectable` | `boolean` | `true` | Whether code text is selectable (adds `select-none` when false). |
| `codeBlockHideCopy` | `boolean` | `false` | Globally hide built‑in copy buttons (you can add your own via actions). |
| `codeBlockHideDownload` | `boolean` | `false` | Globally hide built‑in download buttons. |
All unrecognised props are ignored (no arbitrary HTML injection for safety).
---
## 7. Component Slots & Overrides
`` does not expose custom slots for content fragments (the pipeline is AST-driven). To customize rendering you override tags via the `components` prop:
```ts
import type { Component } from 'vue';
import { StreamMarkdown } from 'streamdown-vue';
const FancyP: Component = {
setup(_, { slots }) { return () => h('p', { class: 'text-pink-600 font-serif' }, slots.default?.()); }
};
```
If a tag is missing from `components` it falls back to the built-in map.
---
## 8. Built‑in Components & Data Attributes
Each semantic node receives a `data-streamdown="name"` attribute to make styling and querying reliable, even if classes are overridden:
| Element / Component | Data Attribute | Notes / Styling Hook |
| -------------------------- | ------------------- | ---------------------------------------------------------------------- |
| Paragraph `