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 GitHub Flavored Markdown including:
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
[!IMPORTANT] Native support for Github style Alert
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 |
[!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:
@import 'tailwindcss';
/* Add Streamdown styles to your Tailwind build */
@source "../node_modules/svelte-streamdown/**/*";
This ensures that all Streamdown's default styling is included in your Tailwind build process.
Note: 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.
<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!`;
// Custom heading component
</script>
{#snippet customH1({ children, props })}
<h1 class="mb-4 text-4xl font-bold text-blue-600" {...props}>
{children}
</h1>
{/snippet}
<Streamdown {content} h1={customH1} />
<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 |
allowElement |
AllowElement | null |
- | Custom element filtering function |
allowedElements |
readonly string[] | null |
- | Whitelist of allowed HTML elements |
disallowedElements |
readonly string[] | null |
- | Blacklist of disallowed HTML elements |
skipHtml |
boolean |
- | Skip HTML parsing entirely |
unwrapDisallowed |
boolean |
- | Unwrap instead of removing disallowed elements |
urlTransform |
UrlTransform | null |
- | Custom URL transformation function |
theme |
Partial<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 |
remarkPlugins |
PluggableList |
- | Additional remark plugins |
rehypePlugins |
PluggableList |
- | Additional rehype plugins |
remarkRehypeOptions |
RemarkRehypeOptions |
- | Remark-rehype conversion options |
customElements |
Record<string, Snippet<[ElementProps]>> |
- | Custom snippets for not handled nodes |
Every single markdown element can be customized with Svelte snippets, giving you complete control over styling and behavior:
<script>
import { Streamdown } from 'svelte-streamdown';
let content = `# Fully Customizable
This heading uses a custom component with your design system!`;
</script>
{#snippet customH1({ children, ...props })}
<h1
class="text-gradient mb-6 bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-4xl font-bold text-transparent"
{...props}
>
{@render children()}
</h1>
{/snippet}
{#snippet customCode({ children, ...props })}
<code class="rounded bg-gray-100 px-2 py-1 font-mono text-sm dark:bg-gray-800" {...props}>
{@render children()}
</code>
{/snippet}
{#snippet customBlockquote({ children, ...props })}
<blockquote
class="border-l-4 border-blue-500 pl-4 text-gray-600 italic dark:text-gray-300"
{...props}
>
{@render children()}
</blockquote>
{/snippet}
<Streamdown {content} h1={customH1} code={customCode} blockquote={customBlockquote} />
Text Elements: h1
, h2
, h3
, h4
, h5
, h6
, p
, strong
, em
, del
Links & Media: a
, img
Lists: ul
, ol
, li
Code: code
, inlineCode
, pre
Tables: table
, thead
, tbody
, tr
, th
, td
Special Content: blockquote
, hr
, alert
, mermaid
, math
, inlineMath
Note: The above elements are supported by Streamdown and should be customized using individual props or the theme system. Use customElements
only for HTML elements not in this list (like div
, span
, section
, article
, etc.).
Each snippet receives { children, ...props }
where props
contains all element attributes and classes.
customElements
RecordThe customElements
prop is specifically for HTML elements that are not handled by the library by default. For elements already supported by Streamdown (like h1
, p
, code
, etc.), use individual props or the theme system instead.
<script>
import { Streamdown } from 'svelte-streamdown';
let content = `# Custom Elements Example
This content contains HTML elements not handled by Streamdown by default:
<div class="special">This is a custom div element</div>
<span class="highlight">This is a custom span element</span>
<section class="wrapper">
<article>This is a custom article inside a section</article>
</section>`;
// Define custom components for unsupported HTML elements
</script>
{#snippet customDiv({ children, props, className, node })}
<div class="rounded-lg border-2 border-blue-500 p-4 {className}" {...props}>
{@render children()}
</div>
{/snippet}
{#snippet customSpan({ children, props, className, node })}
<span class="rounded bg-yellow-200 px-2 py-1 {className}" {...props}>
{@render children()}
</span>
{/snippet}
{#snippet customSection({ children, props, className, node })}
<section class="my-8 rounded-xl bg-gray-50 p-6 {className}" {...props}>
{@render children()}
</section>
{/snippet}
{#snippet customArticle({ children, props, className, node })}
<article class="prose max-w-none {className}" {...props}>
{@render children()}
</article>
{/snippet}
<Streamdown
{content}
customElements={{
div: customDiv,
span: customSpan,
section: customSection,
article: customArticle
}}
/>
customElements
<section>
, <article>
, <aside>
, <nav>
, etc.ElementProps
interfacecustomElements
vs Individual PropsUse the right approach for the right elements:
Individual Props (for supported Streamdown elements):
<Streamdown {content} h1={customH1} p={customP} code={customCode} />
CustomElements Record (for unsupported HTML elements):
<Streamdown
{content}
customElements={{ div: customDiv, span: customSpan, section: customSection }}
/>
Note: For supported elements (h1, p, code, etc.), use individual props or the theme system. For unsupported elements (div, span, section, etc.), use customElements
.
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.
# 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 🤖