A Svelte port of Streamdown by Vercel - an all in one markdown renderer, designed specifically for AI-powered streaming applications.
npm install svelte-streamdown
# or
pnpm add svelte-streamdown
# or
yarn add svelte-streamdown
Perfect for AI-powered applications that need to stream and render markdown content safely and beautifully, with support for incomplete markdown blocks, security hardening, and rich features like code highlighting, math expressions, and interactive diagrams.
Beautiful, responsive typography with built-in Tailwind CSS classes for headings, lists, code blocks, and more. Comes with a complete default theme that works out of the box.
Full support for
code
, [^1]: Reference render in a popover by default. with rich content support and multiline
LaTeX math support through KaTeX:
$$ \sum_{i=1}^n x_i $$
Example:
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug]
D --> B
C --> E[End]
sequenceDiagram
participant User
participant Frontend
participant API
participant Database
User->>Frontend: Submit form
Frontend->>API: POST /api/data
API->>Database: INSERT query
Database-->>API: Success
API-->>Frontend: 200 OK
Frontend-->>User: Show success message
pie title Project Time Allocation
"Development" : 45
"Testing" : 25
"Documentation" : 15
"Meetings" : 15
H1 | H2 | H3 |
---|---|---|
This cell spans 3 columns |
Header 1 | Header 2 | Header 3 |
---|---|---|
This cell spans 2 columns | Normal | |
Normal | Normal | Normal |
Header 1 | Header 2 |
---|---|
This cell spans | Cell A |
two rows ^ | Cell B |
Header 1 | Header 2 |
---|---|
Cell B | Cell A |
--------------- | -------- |
Footer |
Left | Center | Right |
---|---|---|
A | B | C |
| Product Category ||| Sales Data Q1-Q4 2024 |||| | Product | Region || Q1 | Q2 | Q3 | Q4 | | Name | Type | Area | Revenue | Revenue | Revenue | Revenue | |-------------|---------|------------|---------|---------|---------|---------| | Laptop Pro | Electronics | North America || $45,000 | $52,000 | $48,000 | | Laptop Pro ^ | ^ | Europe | $32,000 | $38,000 | $41,000 | $44,000 | | Laptop Pro ^ | ^ | Asia || $28,000 | $35,000 | $42,000 | | Office Chair | Furniture | North America | $15,000 | $18,000 | $16,000 | $17,000 | | Office Chair ^ | ^ | Europe | $12,000 | $14,000 | $15,000 | $16,000 | | Wireless Mouse | Electronics | Global ||| $25,000 | $28,000 | |-------------|---------|------------|---------|---------|---------|---------| | Total Revenue ||| $152,000 | $185,000 | $187,000 | $205,000 |
a. First item
b. Second item
c. Third item
A. First item
B. Second item
C. Third item
i. First item
ii. Second item
iii. Third item
I. First item
II. Second item
III. Third item
First level (numeric) a. Second level (lowercase alpha) i. Third level (lowercase roman) - Fourth level (bullet) I. Fifth level (uppercase roman) A. Sixth level (uppercase alpha)
Back to the first level
[!IMPORTANT] Native support for Github style Alert
: Topic 1 : Description 1
: **Topic 2** : *Description 2*
: Topic 3 : Description 3
: Topic 3 : Description 3
This Svelte port maintains feature parity with the original Streamdown while adapting to Svelte's patterns:
Aspect | Original (React) | Svelte Port |
---|---|---|
Framework | React | Svelte 5 |
Component API | JSX Components | Svelte Snippets |
Styling | Tailwind CSS | Tailwind CSS (compatible) |
Context | React Context | Svelte Context |
Build System | Vite/React | Vite/SvelteKit |
TypeScript | Full TS support | Full TS support |
Engine | Remark / Rehype + marked | marked only |
[!NOTE] Streamdown comes with built-in Tailwind CSS classes for beautiful default styling. To ensure all styles are included in your build, add the following to your
app.css
or main CSS file: This setup is primarily necessary if you're using Tailwind CSS v4's new@source
directive or if you have aggressive purging enabled in older versions. If you're using standard Tailwind CSS v3+ with default purging, Streamdown's styles should be automatically included when the component is imported and used in your application.This ensures that all Streamdown's default styling is included in your Tailwind build process.
@import 'tailwindcss';
/* Add Streamdown styles to your Tailwind build */
@source "../node_modules/svelte-streamdown/**/*";
Streamdown includes an animation system designed specifically for streaming AI content, providing smooth and engaging visual feedback as text appears on screen.
The animation system works by:
Choose from 4 distinct animation styles:
fade
A clean fade-in effect where text smoothly appears from transparent to opaque.
blur
Text starts slightly blurred and comes into focus while fading in, creating a smooth reveal effect.
slideUp
Text slides up from below while fading in, creating a dynamic upward motion.
slideDown
Text slides down from above while fading in, creating a dynamic downward motion.
[!TIP] For production applications where the LLM is not streaming (static content), disable animations entirely by setting
animation.enabled = false
to minimize DOM elements and improve performance.If using AI SDK mind to smooth stream the content to using word-level tokenization to avoid partial words not being animated.
[!WARNING] Character-level tokenization (
tokenize: 'char'
) creates significantly more DOM elements than word-level tokenization. Use character tokenization sparingly and only when the typewriter effect is essential for your user experience.
<script>
import { Streamdown } from 'svelte-streamdown';
let content = `# Hello World
This is a **bold** text and this is *italic*.
\`\`\`javascript
console.log('Hello from Streamdown!');
\`\`\`
`;
</script>
<Streamdown {content} />
<script>
import { Streamdown } from 'svelte-streamdown';
let content = `# Custom Components Example
This heading will use a custom component!`;
</script>
<Streamdown {content}>
{#snippet heading({ children })}
<h1 class="mb-4 text-4xl font-bold text-blue-600">
{@render children()}
</h1>
{/snippet}
</Streamdown>
<script>
import { Streamdown } from 'svelte-streamdown';
let markdown = `
[Safe Link](https://trusted-domain.com/page)`;
</script>
<Streamdown
{content}
allowedImagePrefixes={['https://trusted-domain.com']}
allowedLinkPrefixes={['https://trusted-domain.com']}
/>
Prop | Type | Default | Description |
---|---|---|---|
content |
string |
- | Required. The markdown content to render |
class |
string |
- | CSS class names for the wrapper element |
parseIncompleteMarkdown |
boolean |
true |
Parse and fix incomplete markdown syntax |
defaultOrigin |
string |
- | Default origin for relative URLs |
allowedLinkPrefixes |
string[] |
['*'] |
Allowed URL prefixes for links |
allowedImagePrefixes |
string[] |
['*'] |
Allowed URL prefixes for images |
skipHtml |
boolean |
- | Skip HTML parsing entirely |
unwrapDisallowed |
boolean |
- | Unwrap instead of removing disallowed elements |
urlTransform |
UrlTransform | null |
- | Custom URL transformation function |
theme |
DeepPartial<Theme> |
- | Custom theme overrides |
baseTheme |
'tailwind' | 'shadcn' |
'tailwind' |
Base theme to use before applying overrides |
mergeTheme |
boolean |
true |
Whether to merge theme with base theme |
shikiTheme |
BundledTheme |
'github-light' |
Code highlighting theme |
mermaidConfig |
MermaidConfig |
- | Mermaid diagram configuration |
katexConfig |
KatexOptions | ((inline: boolean) => KatexOptions) |
- | KaTeX math rendering options |
animation |
AnimationConfig |
- | Animation configuration for streaming content |
animation.enabled |
boolean |
false |
Enable/disable animations |
animation.type |
'fade' | 'blur' | 'typewriter' | 'slideUp' | 'slideDown' |
'blur' |
Animation style for text appearance |
animation.duration |
number |
500 |
Animation duration in milliseconds |
animation.timingFunction |
'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'linear' |
'ease-in' |
CSS timing function for animations |
animation.tokenize |
'word' | 'char' |
'word' |
Tokenization method for text animations |
animation.animateOnMount |
boolean |
false |
Run the token animation on mount or not, useful if you render the Streamdown component in the same time as the first token is receive from the LLM |
extensions |
Array<Extension> |
[] |
Custom marked tokenizers to render special markdown blocks or inline tokens |
children |
Snippet<[{token:GenericToken, streamdown: StreamdownContext, children: Snippet |
undefined |
Snippet used to render element that are not supported by Streamdown and tokenized by your custom extensions |
Text Elements: heading
, p
, strong
, em
, del
Links & Media: a
, img
Lists: ul
, ol
, li
Code: code
, codeSpan
Tables: table
, thead
, tbody
, tr
, th
, td
, tfoot
Special Content: blockquote
, hr
, alert
, mermaid
, math
, footnoteRef
Note: The above elements are supported by Streamdown and should be customized using individual props or the theme system.
Streamdown comes with two built-in themes:
Beyond custom snippets, Streamdown provides a granular theming system that lets you customize every part of every component without writing custom snippets. You can use the built-in themes (default and shadcn) or create completely custom themes using the mergeTheme
utility.
Every component has multiple themeable parts. For example, the code
component has:
code: {
base: 'bg-gray-100 rounded p-2 font-mono text-sm', // Main code block
container: 'my-4 w-full overflow-hidden rounded-xl border', // Wrapper container
header: 'flex items-center justify-between bg-gray-100/80', // Header with language
button: 'cursor-pointer p-1 text-gray-600 transition-all', // Copy button
language: 'ml-1 font-mono lowercase', // Language label
pre: 'overflow-x-auto font-mono p-0 bg-gray-100/40' // Pre element
}
<script>
import { Streamdown } from 'svelte-streamdown';
let content = `# Custom Theme Example
\`\`\`javascript
console.log('Beautiful code blocks!');
\`\`\`
> This blockquote is also themed
| Header 1 | Header 2 |
|----------|----------|
| Cell 1 | Cell 2 |
`;
// Custom theme overrides
let customTheme = {
code: {
container: 'my-6 rounded-2xl border-2 border-purple-200 shadow-lg',
header: 'bg-purple-50 text-purple-700 font-medium',
button: 'text-purple-600 hover:text-purple-800 hover:bg-purple-100'
},
blockquote: {
base: 'border-l-8 border-purple-400 bg-purple-50 p-4 italic text-purple-800'
},
table: {
base: 'border-purple-200 shadow-md',
container: 'my-6 rounded-lg overflow-hidden'
},
th: {
base: 'bg-purple-100 px-6 py-3 text-purple-900 font-bold'
},
td: {
base: 'px-6 py-3 border-purple-100'
}
};
</script>
<Streamdown {content} theme={customTheme} />
Each component supports multiple themeable parts:
Headings (h1
-h6
): base
Text Elements (p
, strong
, em
, del
): base
Lists (ul
, ol
, li
): base
Links (a
): base
, blocked
(for blocked/unsafe links)
Code (code
): base
, container
, header
, button
, language
, skeleton
, pre
Inline Code (inlineCode
): base
Images (img
): container
, base
, downloadButton
Tables (table
, thead
, tbody
, tr
, th
, td
): base
, container
(table only)
Blockquotes (blockquote
): base
Alerts (alert
): base
, title
, icon
, plus type-specific styles (note
, tip
, warning
, caution
, important
)
Mermaid (mermaid
): base
, downloadButton
Math (math
, inlineMath
): base
Other (hr
, sup
, sub
): base
Themes are intelligently merged using Tailwind's class merging utility, so you only need to override the specific parts you want to customize while keeping the default styling for everything else.
Streamdown is extensible through the use of custom extensions.
An extension is an object that has a name
, a level
and a tokenizer
function.
name
: The name of the extensionlevel
: The level of the extension, can be block
or inline
tokenizer
: The tokenizer function, see marked for more informationTo render the extension custom tokens, you can then simply use the children
snippet.
<script lang="ts">
import { Streamdown, type Extension } from 'svelte-streamdown';
const markedCollapsible: Extension = {
name: 'collapsible',
level: 'block',
tokenizer(this, src) {
// Match [detail]...[detail] blocks (case insensitive)
const detailMatch = src.match(/^\[detail\](.*?)\[detail\]/is);
if (detailMatch) {
const content = detailMatch[1] || '';
const tokens = this.lexer.blockTokens(content);
return {
type: 'detail',
raw: detailMatch[0], // The entire matched string including tags
tokens
};
}
return undefined;
}
};
</script>
<Streamdown
extensions={[markedCollapsible]}
content={`
[detail]
This is a collapsible **section**
[detail]`}
>
{#snippet children({ token, streamdown, children })}
{#if token.type === 'detail'}
<details>
<summary> Detail </summary>
<div>
{@render children()}
</div>
</details>
{/if}
{/snippet}
</Streamdown>
# Clone the repository
git clone <repository-url>
cd svelte-streamdown
# Install dependencies
pnpm install
# Start development server
pnpm dev
# Run tests
pnpm test
# Build for production
pnpm build
# Build the library
pnpm build
# Preview the showcase app
pnpm preview
Contributions are welcome! This is a port of the original Streamdown project, so please:
MIT
Made with ❤️ and 🤖