Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Svelte port of Streamdown

License

NotificationsYou must be signed in to change notification settings

beynar/svelte-streamdown

Repository files navigation

npm version

ASvelte port ofStreamdown by Vercel - an all in one markdown renderer, designed specifically for AI-powered streaming applications.

📦 Installation

npm install svelte-streamdown# orpnpm add svelte-streamdown# oryarn add svelte-streamdown

🚀 Overview

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.

✨ Main Features

🔄 Streaming-Optimized

  • Incomplete Markdown Parsing: Handles unterminated blocks gracefully
  • Progressive Rendering: Perfect for streaming AI responses
  • Real-time Updates: Optimized for dynamic content
  • Smooth Animations: Animate tokens and blocks as they are streamed.

🔒 Security Hardening

  • Image Origin Control: Whitelist allowed image sources
  • Link Safety: Control link destinations

🎯 Fully Customizable Components & Theming

  • Every component customizable with Svelte snippets
  • Granular theming system - customize every part of every component
  • Override default styling and behavior for any markdown element
  • Full control over rendering with type-safe props
  • Seamless integration with your design system

🎨 Built-in Typography Styles

Beautiful, responsive typography withbuilt-in Tailwind CSS classes for headings, lists, code blocks, and more. Comes with a complete default theme that works out of the box.

📝 Extensive Markdown Features

Full support for

  • Basic text marks:bold,italic,code,Strikethrough
  • Subscript and ^Superscript^
  • Links
  • Headings (H1–H6)
  • Blockquotes
  • Github alert
  • Ordered & unordered lists (including roman, alpha, nested)
  • Task lists ([ ] and [x])
  • Code blocks
  • Mermaid diagrams
  • Math$expressions$
  • Escaping currency symbols ($140)
  • Complex tables
  • Footnotes1
  • Inline citations [ref] [ref2]
  • MDX components (embed custom Svelte components)

Note

🧠AI Prompting Tip: For best results, use ourcomprehensive prompt covering all supported markdown features.

💻 Interactive Code Blocks

  • Syntax highlighting powered by Shiki
  • Copy-to-clipboard functionality
  • Support any Shiki themes

🔢 Mathematical Expressions

LaTeX math support through KaTeX:

  • Perfect rendering for scientific content
  • Inline math:$E = mc^2$
  • Block math:

$$\sum_{i=1}^n x_i$$

🧜‍♀️ Mermaid Diagrams

  • Render Mermaid diagrams from code blocks
  • Incremental rendering during streaming content
  • Pan and Zoom
  • Full screen mode

Example:

graph TD    A[Start] --> B{Is it working?}    B -->|Yes| C[Great!]    B -->|No| D[Debug]    D --> B    C --> E[End]
Loading
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
Loading
pie title Project Time Allocation    "Development" : 45    "Testing" : 25    "Documentation" : 15    "Meetings" : 15
Loading

Complex table support

Colspan

H1H2H3
This cell spans 3 columns
Header 1Header 2Header 3
This cell spans 2 columnsNormal
NormalNormalNormal

Rowspan

Header 1Header 2
This cell spansCell A
two rows ^Cell B

Footer

Header 1Header 2
Cell BCell A
-----------------------
Footer

Column alignment

LeftCenterRight
ABC

Multiple headers and very complex layout

| Product Category ||| Sales Data Q1-Q4 2024 ||||| Product | Region || Q1 | Q2 | Q3 | Q4 |

NameTypeAreaRevenueRevenueRevenueRevenue
Laptop ProElectronicsNorth 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 ChairFurnitureNorth America$15,000$18,000$16,000$17,000
Office Chair ^^Europe$12,000$14,000$15,000$16,000
Wireless MouseElectronicsGlobal$25,000$28,000
----------------------------------------------------------------------
Total Revenue$152,000$185,000$187,000$205,000

Complex list support

decimal

  1. First item
  2. Second item
  3. Third item

lower-alpha

a. First item
b. Second item
c. Third item

upper-alpha

A. First item
B. Second item
C. Third item

lower-roman

i. First item
ii. Second item
iii. Third item

upper-roman

I. First item
II. Second item
III. Third item

Nested Lists

  1. 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)

  2. Back to the first level

Task List

  • Uncompleted task
  • Completed task
  • Another uncompleted task
    • Nested uncompleted subtask
    • Nested completed subtask

Alert Support

Important

Native support for Github style Alert

Description List

:   Topic 1   :  Description 1: **Topic 2** : *Description 2*:   Topic 3   :  Description 3:   Topic 3   :  Description 3

Citation Support

Streamdown supports inline citations that allow you to reference external sources and display them in interactive popovers. Citations work out-of-the-box with a simple object structure and support nested references like this[cloudflare.website, vercel] will render into [cloudflare.website, vercel]

To enable inline citations, pass asources object as a prop to theStreamdown component.

Basic Usage

<script>import {Streamdown }from'svelte-streamdown';let content=`According to [smith2023], AI is advancing rapidly. See also [nested.subsection] for related work.`;let sources= {"smith2023": {      title:"AI Research Paper",      url:"https://example.com/paper",      content:"Detailed content of the citation..."    },"nested": {"subsection": {        title:"Nested Citation",        url:"https://example.com/nested"      }    }  };</script><Streamdown {content} {sources} />

Default Citation Structure

Citations work with objects containing these properties:

  • title (or name or author): Display title for the citation
  • url (or href, url, link or source): Link to the source
  • content (or text, summary or excerpt): Rich content to display in carousel mode

Display Modes

Streamdown offers two ways to display citations:

  • List View: Shows all citations in a compact list format
  • Carousel View (default): Step-through navigation for multiple citations with full content display

You can control the display mode using theinlineCitationsMode prop:

<!-- List view --><Streamdown {content} {sources}inlineCitationsMode="list" /><!-- Carousel view (default) --><Streamdown {content} {sources}inlineCitationsMode="carousel" />

Citation Popovers

Citations appear as clickable buttons that open popovers when clicked. The popover shows:

  • Source title and URL (when available)
  • Favicon from the source domain
  • Rich content (in carousel mode)
  • Navigation controls (in carousel mode for multiple citations)

Custom Citation Rendering

If your citation data structure doesn't match the default format, you can customize how citations are rendered usinginlineCitationPreview,inlineCitationContent orinlineCitationPopover snippets:

<Streamdown {content} {sources}>  {#snippetinlineCitationPreview({token })}<!-- Customize the clickable citation button -->         {token.keys[0]}  {/snippet}  {#snippetinlineCitationContent({source,key,token })}<!-- Customize content displayed in popover -->    <divclass="custom-content">      <h4>{source.customTitle||key}</h4>      <p>{source.customDescription}</p>    </div>  {/snippet}</Streamdown>

These snippets allow you to:

  • inlineCitationPreview: Customize the content of the clickable button that appears in the text
  • inlineCitationContent: Customize how individual citation content is displayed within popovers
  • inlineCitationPopover: Completely customize the list of citations

🔄 Differences from Original React Version

This Svelte port maintains feature parity with the originalStreamdown while adapting to Svelte's patterns:

AspectOriginal (React)Svelte Port
FrameworkReactSvelte 5
Component APIJSX ComponentsSvelte Snippets
StylingTailwind CSSTailwind CSS (compatible)
ContextReact ContextSvelte Context
Build SystemVite/ReactVite/SvelteKit
TypeScriptFull TS supportFull TS support
EngineRemark / Rehype + markedmarked only

Tailwind CSS Setup

Note

Streamdown comes withbuilt-in Tailwind CSS classes for beautiful default styling. To ensure all styles are included in your build, add the following to yourapp.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/**/*";

🎭 Animation System

Streamdown includes an animation system designed specifically for streaming AI content, providing smooth and engaging visual feedback as text appears on screen.

How It Works

The animation system works by:

  1. Tokenization: Text is broken down into tokens (words or characters) based on your configuration
  2. Sequential Animation: Each token animates as it is received
  3. Block-level Animation: Entire blocks (paragraphs, headings, code blocks) animate as units

Animation Types

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 settinganimation.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.

🚀 Quick Start

Basic Usage

<script>import {Streamdown }from'svelte-streamdown';let content=`# Hello WorldThis is a **bold** text and this is *italic*.\`\`\`javascriptconsole.log('Hello from Streamdown!');\`\`\``;</script><Streamdown {content} />

Advanced Usage with Custom Components

<script>import {Streamdown }from'svelte-streamdown';let content=`# Custom Components ExampleThis heading will use a custom component!`;</script><Streamdown {content}>{#snippetheading({children })}<h1class="mb-4 text-4xl font-bold text-blue-600">{@renderchildren()}</h1>{/snippet}</Streamdown>

Security Configuration

<script>import {Streamdown }from'svelte-streamdown';let markdown=`![Safe Image](https://trusted-domain.com/image.jpg)[Safe Link](https://trusted-domain.com/page)`;</script><Streamdown{content}allowedImagePrefixes={['https://trusted-domain.com']}allowedLinkPrefixes={['https://trusted-domain.com']}/>

📋 Props API

PropTypeDefaultDescription
contentstring-Required. The markdown content to render
sourcesRecord<string, any>-Citation data object for inline citations
classstring-CSS class names for the wrapper element
parseIncompleteMarkdownbooleantrueParse and fix incomplete markdown syntax
defaultOriginstring-Default origin for relative URLs
allowedLinkPrefixesstring[]['*']Allowed URL prefixes for links
allowedImagePrefixesstring[]['*']Allowed URL prefixes for images
skipHtmlboolean-Skip HTML parsing entirely
unwrapDisallowedboolean-Unwrap instead of removing disallowed elements
urlTransformUrlTransform | null-Custom URL transformation function
themeDeepPartial<Theme>-Custom theme overrides
baseTheme'tailwind' | 'shadcn''tailwind'Base theme to use before applying overrides
mergeThemebooleantrueWhether to merge theme with base theme
shikiThemeBundledTheme'github-light'Code highlighting theme
mermaidConfigMermaidConfig-Mermaid diagram configuration
katexConfigKatexOptions | ((inline: boolean) => KatexOptions)-KaTeX math rendering options
animationAnimationConfig-Animation configuration for streaming content
animation.enabledbooleanfalseEnable/disable animations
animation.type'fade' | 'blur' | 'typewriter' | 'slideUp' | 'slideDown''blur'Animation style for text appearance
animation.durationnumber500Animation 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.animateOnMountbooleanfalseRun 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
extensionsArray<Extension>[]Custom marked tokenizers to render special markdown blocks or inline tokens
mdxComponentsRecord<string, Component>{}Map of MDX component names to Svelte components (e.g.,{ Card, Button })
childrenSnippet<[{token:GenericToken, streamdown: StreamdownContext, children: SnippetundefinedSnippet used to render elements not supported by Streamdown, custom extensions, and MDX components

All Available Customizable Elements:

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,inlineCitation

MDX Components: Handled via a singlemdx snippet that receivestoken,props, andchildren. Usetoken.tagName to differentiate between components.

Note: The above elements aresupported by Streamdown and should be customized using individual props or the theme system. MDX components require themdx snippet.

🎨 Theming System

Built-in Themes

Streamdown comes with two built-in themes:

  • Default Theme: The standard theme with gray-based colors
  • Shadcn Theme: A theme that uses shadcn/ui design tokens for seamless integration with shadcn-based projects

Beyond custom snippets, Streamdown provides agranular 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 themergeTheme utility.

Theme Structure

Every component has multiple themeable parts. For example, thecode component has:

code:{base:'bg-gray-100 rounded p-2 font-mono text-sm',// Main code blockcontainer:'my-4 w-full overflow-hidden rounded-xl border',// Wrapper containerheader:'flex items-center justify-between bg-gray-100/80',// Header with languagebutton:'cursor-pointer p-1 text-gray-600 transition-all',// Copy buttonlanguage:'ml-1 font-mono lowercase',// Language labelpre:'overflow-x-auto font-mono p-0 bg-gray-100/40'// Pre element}

Using Custom Themes

<script>import {Streamdown }from'svelte-streamdown';let content=`# Custom Theme Example\`\`\`javascriptconsole.log('Beautiful code blocks!');\`\`\`> This blockquote is also themed| Header 1 | Header 2 ||----------|----------|| Cell 1   | Cell 2   |`;// Custom theme overrideslet 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} />

All Themeable Components

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

Theme Merging

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.

🧩 MDX Component Support

Streamdown supports MDX-style JSX components, allowing you to embed custom Svelte components directly in your markdown content.

Basic Usage

<script>import {Streamdown }from'svelte-streamdown';let content=`# Using MDX Components<Card title="Hello" count={42}>This is **markdown content** inside a component!</Card><Button label="Click me" active={true} />`;</script><Streamdown {content}>  {#snippetmdx({token,props,children })}    {#iftoken.tagName==='Card'}      <divclass="rounded-lg border border-gray-200 p-4 shadow-sm">        <h3class="text-xl font-bold">{props.title}</h3>        <pclass="text-gray-600">Count: {props.count}</p>        <divclass="mt-2">          {@renderchildren()}        </div>      </div>    {:elseiftoken.tagName==='Button'}      <buttonclass="rounded px-4 py-2{props.active?'bg-blue-500 text-white':'bg-gray-200'}">        {props.label}      </button>    {:else}      {@renderchildren()}    {/if}  {/snippet}</Streamdown>

Alternative: Using Svelte Components Directly

Instead of using themdx snippet with conditional logic, you can pass Svelte components directly using themdxComponents prop:

<script>import {Streamdown }from'svelte-streamdown';importCardfrom'./Card.svelte';importButtonfrom'./Button.svelte';let content=`# Using MDX Components<Card title="Hello" count={42}>This is **markdown content** inside a component!</Card><Button label="Click me" active={true} />`;</script><Streamdown {content}mdxComponents={{Card,Button }} />

Your Svelte components (Card.svelte,Button.svelte) should accept props and achildren snippet:

<!-- Card.svelte --><script>let { title, count, children }=$props();</script><divclass="rounded-lg border border-gray-200 p-4 shadow-sm">  <h3class="text-xl font-bold">{title}</h3>  <pclass="text-gray-600">Count: {count}</p>  <divclass="mt-2">    {@renderchildren()}  </div></div>
<!-- Button.svelte --><script>let { label, active }=$props();</script><buttonclass="rounded px-4 py-2{active?'bg-blue-500 text-white':'bg-gray-200'}">  {label}</button>

This approach is cleaner when you have standalone component files, while themdx snippet approach is better for inline component definitions or when you need shared logic across components.

Supported Syntax

Self-closing components:

<Componentattr="value"count={42}enabled={true} />

Components with markdown children:

<Componenttitle="Hello">#This is a headingThis**markdown** content will be parsed!</Component>

Attribute Types

MDX components support three attribute value types:

  • Strings:attr="hello""hello"
  • Numbers:count={42} orvalue={3.14}42,3.14
  • Booleans:active={true} ordisabled={false}true,false
  • Expressions:value={variableName}"variableName" (stored as string)

Component Naming

  • Component namesmust start with a capital letter (PascalCase)
  • Valid:<Card />,<MyComponent />,<Component123 />
  • Invalid:<card />,<myComponent /> (these are treated as HTML)

Streaming Safety

MDX components are streaming-safe. Incomplete components are automatically handled during AI streaming:

  • Incomplete tags like<Component attr not rendered to prevent runtime errors
  • Unclosed components like<Card>content are auto-closed with</Card>
  • Malformed attributes are escaped to prevent rendering errors

This ensures your UI remains stable even when receiving partial markdown from streaming AI responses.

Component Props

Themdx snippet receives three parameters:

  • token: The full MdxToken withtagName,attributes,selfClosing, etc.
  • props: Object containing all parsed attributes (e.g.,props.title,props.count)
  • children: Snippet containing parsed markdown content

Usetoken.tagName to determine which component is being rendered:

<!-- Markdown: <Card title="Hello" count={5}>Content</Card> --><Streamdown {content}>  {#snippetmdx({token,props,children })}    {#iftoken.tagName==='Card'}      <div>        <h3>{props.title}</h3>        <span>Count: {props.count}</span>        {@renderchildren()}      </div>    {:elseiftoken.tagName==='Alert'}      <divclass="alert alert-{props.type}">        {@renderchildren()}      </div>    {:else}<!-- Fallback for unknown components -->      {@renderchildren()}    {/if}  {/snippet}</Streamdown>

💉 Extensibility

Streamdown is extensible through the use of custom extensions.

An extension is an object that has aname, alevel and atokenizer function.

  • name: The name of the extension
  • level: The level of the extension, can beblock orinline
  • tokenizer: The tokenizer function, seemarked for more information

To render the extension custom tokens, you can then simply use thechildren snippet.

Example

<scriptlang="ts">import {Streamdown,typeExtension }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 tagstokens};}returnundefined;}};</script><Streamdownextensions={[markedCollapsible]}content={`[detail]This is a collapsible **section**[detail]`}>{#snippetchildren({token,streamdown,children })}{#iftoken.type==='detail'}<details><summary> Detail </summary><div>{@renderchildren()}</div></details>{/if}{/snippet}</Streamdown>

🛠️ Development

Setup

# Clone the repositorygit clone<repository-url>cd svelte-streamdown# Install dependenciespnpm install# Start development serverpnpm dev# Run testspnpmtest# Build for productionpnpm build

Building

# Build the librarypnpm build# Preview the showcase apppnpm preview

🤝 Contributing

Contributions are welcome! This is a port of the original Streamdown project, so please:

  1. Check theoriginal Streamdown repository for upstream changes
  2. Ensure compatibility with the original API
  3. Maintain feature parity where possible
  4. Add tests for new features if you want

📄 License

MIT

🙏 Acknowledgments

  • Original Streamdown:Vercel for creating the original React component
  • Svelte Community: For the amazing framework that made this port possible
  • All Contributors: For helping improve and maintain this project

Made with ❤️ and 🤖

Footnotes

  1. Reference render in a popover by default.withrichcontent supportand multiline


[8]ページ先頭

©2009-2025 Movatter.jp