An open API service indexing awesome lists of open source software.

https://github.com/humanspeak/svelte-markdown

๐Ÿ“ Fast, lightweight Markdown renderer component for Svelte applications with full CommonMark support
https://github.com/humanspeak/svelte-markdown

javascript markdown markdown-parser markdown-to-html svelte sveltekit typescript ui-components web-components

Last synced: 27 days ago
JSON representation

๐Ÿ“ Fast, lightweight Markdown renderer component for Svelte applications with full CommonMark support

Awesome Lists containing this project

README

          

# @humanspeak/svelte-markdown

A powerful, customizable markdown renderer for Svelte with TypeScript support. Built as a successor to the original svelte-markdown package by Pablo Berganza, now maintained and enhanced by Humanspeak, Inc.

[![NPM version](https://img.shields.io/npm/v/@humanspeak/svelte-markdown.svg)](https://www.npmjs.com/package/@humanspeak/svelte-markdown)
[![Build Status](https://github.com/humanspeak/svelte-markdown/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/humanspeak/svelte-markdown/actions/workflows/npm-publish.yml)
[![Coverage Status](https://coveralls.io/repos/github/humanspeak/svelte-markdown/badge.svg?branch=main)](https://coveralls.io/github/humanspeak/svelte-markdown?branch=main)
[![License](https://img.shields.io/npm/l/@humanspeak/svelte-markdown.svg)](https://github.com/humanspeak/svelte-markdown/blob/main/LICENSE)
[![Downloads](https://img.shields.io/npm/dm/@humanspeak/svelte-markdown.svg)](https://www.npmjs.com/package/@humanspeak/svelte-markdown)
[![CodeQL](https://github.com/humanspeak/svelte-markdown/actions/workflows/codeql.yml/badge.svg)](https://github.com/humanspeak/svelte-markdown/actions/workflows/codeql.yml)
[![Install size](https://packagephobia.com/badge?p=@humanspeak/svelte-markdown)](https://packagephobia.com/result?p=@humanspeak/svelte-markdown)
[![Code Style: Trunk](https://img.shields.io/badge/code%20style-trunk-blue.svg)](https://trunk.io)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](http://www.typescriptlang.org/)
[![Types](https://img.shields.io/npm/types/@humanspeak/svelte-markdown.svg)](https://www.npmjs.com/package/@humanspeak/svelte-markdown)
[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/humanspeak/svelte-markdown/graphs/commit-activity)

## Features

- ๐Ÿ”’ **Secure HTML parsing** via HTMLParser2 with XSS protection
- ๐Ÿš€ Full markdown syntax support through Marked
- ๐Ÿ’ช Complete TypeScript support with strict typing
- ๐Ÿ”„ Svelte 5 runes compatibility
- โœ‚๏ธ Inline snippet overrides โ€” customize renderers without separate files
- ๐ŸŽจ Customizable component rendering system
- โ™ฟ WCAG 2.1 accessibility compliance
- ๐ŸŽฏ GitHub-style slug generation for headers
- ๐Ÿงช Comprehensive test coverage (vitest and playwright)
- ๐Ÿงฉ First-class marked extensions support via `extensions` prop (e.g., KaTeX math, alerts)
- โšก Intelligent token caching (50-200x faster re-renders)
- ๐Ÿ“ก LLM streaming mode with incremental rendering (~1.6ms avg per update)
- ๐Ÿ–ผ๏ธ Smart image lazy loading with fade-in animation

## Installation

```bash
npm i -S @humanspeak/svelte-markdown
```

Or with your preferred package manager:

```bash
pnpm add @humanspeak/svelte-markdown
yarn add @humanspeak/svelte-markdown
```

## Basic Usage

```svelte

import SvelteMarkdown from '@humanspeak/svelte-markdown'

const source = `
# This is a header

This is a paragraph with **bold** and <em>mixed HTML</em>.

* List item with \`inline code\`
* And a [link](https://svelte.dev)
* With nested items
* Supporting full markdown
`

```

## TypeScript Support

The package is written in TypeScript and includes full type definitions:

```typescript
import type {
Renderers,
Token,
TokensList,
SvelteMarkdownOptions,
MarkedExtension
} from '@humanspeak/svelte-markdown'
```

## Exports for programmatic overrides

You can import renderer maps and helper keys to selectively override behavior.

```ts
import SvelteMarkdown, {
// Maps
defaultRenderers, // markdown renderer map
Html, // HTML renderer map

// Keys
rendererKeys, // markdown renderer keys (excludes 'html')
htmlRendererKeys, // HTML renderer tag names

// Utility components
Unsupported, // markdown-level unsupported fallback
UnsupportedHTML // HTML-level unsupported fallback
} from '@humanspeak/svelte-markdown'

// Example: override a subset
const customRenderers = {
...defaultRenderers,
link: CustomLink,
html: {
...Html,
span: CustomSpan
}
}

// Optional: iterate keys when building overrides dynamically
for (const key of rendererKeys) {
// if (key === 'paragraph') customRenderers.paragraph = MyParagraph
}
for (const tag of htmlRendererKeys) {
// if (tag === 'div') customRenderers.html.div = MyDiv
}
```

Notes

- `rendererKeys` intentionally excludes `html`. Use `htmlRendererKeys` for HTML tag overrides.
- `Unsupported` and `UnsupportedHTML` are available if you want a pass-through fallback strategy.

## Helper utilities for allow/deny strategies

These helpers make it easy to either allow only a subset or exclude only a subset of renderers without writing huge maps by hand.

- **HTML helpers**
- `buildUnsupportedHTML()`: returns a map where every HTML tag uses `UnsupportedHTML`.
- `allowHtmlOnly(allowed)`: enable only the provided tags; others use `UnsupportedHTML`.
- Accepts tag names like `'strong'` or tuples like `['div', MyDiv]` to plug in custom components.
- `excludeHtmlOnly(excluded, overrides?)`: disable only the listed tags (mapped to `UnsupportedHTML`), with optional overrides for non-excluded tags using tuples.
- **Markdown helpers (non-HTML)**
- `buildUnsupportedRenderers()`: returns a map where all markdown renderers (except `html`) use `Unsupported`.
- `allowRenderersOnly(allowed)`: enable only the provided markdown renderer keys; others use `Unsupported`.
- Accepts keys like `'paragraph'` or tuples like `['paragraph', MyParagraph]` to plug in custom components.
- `excludeRenderersOnly(excluded, overrides?)`: disable only the listed markdown renderer keys, with optional overrides for non-excluded keys using tuples.

### HTML helpers in context

The HTML helpers return an `HtmlRenderers` map to be used inside the `html` key of the overall `renderers` map. They do not replace the entire `renderers` object by themselves.

Basic: keep markdown defaults, allow only a few HTML tags (others become `UnsupportedHTML`):

```ts
import SvelteMarkdown, { defaultRenderers, allowHtmlOnly } from '@humanspeak/svelte-markdown'

const renderers = {
...defaultRenderers, // keep markdown defaults
html: allowHtmlOnly(['strong', 'em', 'a']) // restrict HTML
}
```

Allow a custom component for one tag while allowing others with defaults:

```ts
import SvelteMarkdown, { defaultRenderers, allowHtmlOnly } from '@humanspeak/svelte-markdown'

const renderers = {
...defaultRenderers,
html: allowHtmlOnly([['div', MyDiv], 'a'])
}
```

Exclude just a few HTML tags; keep all other HTML tags as defaults:

```ts
import SvelteMarkdown, { defaultRenderers, excludeHtmlOnly } from '@humanspeak/svelte-markdown'

const renderers = {
...defaultRenderers,
html: excludeHtmlOnly(['span', 'iframe'])
}

// Or exclude 'span', but override 'a' to CustomA
const renderersWithOverride = {
...defaultRenderers,
html: excludeHtmlOnly(['span'], [['a', CustomA]])
}
```

Disable all HTML quickly (markdown defaults unchanged):

```ts
import SvelteMarkdown, { defaultRenderers, buildUnsupportedHTML } from '@humanspeak/svelte-markdown'

const renderers = {
...defaultRenderers,
html: buildUnsupportedHTML()
}
```

### Markdown-only (non-HTML) scenarios

Allow only paragraph and link with defaults, disable others:

```ts
import { allowRenderersOnly } from '@humanspeak/svelte-markdown'

const md = allowRenderersOnly(['paragraph', 'link'])
```

Exclude just link; keep others as defaults:

```ts
import { excludeRenderersOnly } from '@humanspeak/svelte-markdown'

const md = excludeRenderersOnly(['link'])
```

Disable all markdown renderers (except `html`) quickly:

```ts
import { buildUnsupportedRenderers } from '@humanspeak/svelte-markdown'

const md = buildUnsupportedRenderers()
```

### Combine HTML and Markdown helpers

You can combine both maps in `renderers` for `SvelteMarkdown`.

```svelte

import SvelteMarkdown, { allowRenderersOnly, allowHtmlOnly } from '@humanspeak/svelte-markdown'

const renderers = {
// Only allow a minimal markdown set
...allowRenderersOnly(['paragraph', 'link']),

// Configure HTML separately (only strong/em/a)
html: allowHtmlOnly(['strong', 'em', 'a'])
}

const source = `# Title\n\nThis has <strong>HTML</strong> and [a link](https://example.com).`

```

## Custom Renderer Example

Here's a complete example of a custom renderer with TypeScript support:

```svelte

import type { Snippet } from 'svelte'

interface Props {
children?: Snippet
href?: string
title?: string
}

const { href = '', title = '', children }: Props = $props()


{@render children?.()}

```

If you would like to extend other renderers please take a look inside the [renderers folder](https://github.com/humanspeak/svelte-markdown/tree/main/src/lib/renderers) for the default implentation of them. If you would like feature additions please feel free to open an issue!

## Snippet Overrides (Svelte 5)

For simple tweaks โ€” adding a class, changing an attribute, wrapping in a div โ€” you can override renderers inline with Svelte 5 snippets instead of creating separate component files:

```svelte

import SvelteMarkdown from '@humanspeak/svelte-markdown'

const source = '# Hello\n\nA paragraph with [a link](https://example.com).'

{#snippet paragraph({ children })}

{@render children?.()}


{/snippet}

{#snippet heading({ depth, children })}
{#if depth === 1}

{@render children?.()}


{:else}

{@render children?.()}


{/if}
{/snippet}

{#snippet link({ href, title, children })}

{@render children?.()}

{/snippet}

{#snippet code({ lang, text })}

{text}

{/snippet}

```

### How it works

- **Container renderers** (paragraph, heading, blockquote, list, etc.) receive a `children` snippet for nested content
- **Leaf renderers** (code, image, hr, br) receive only data props โ€” no `children`
- **Precedence**: snippet > component renderer > default. If both a snippet and a `renderers.paragraph` component are provided, the snippet wins

### HTML tag snippets

HTML tag snippets use an `html_` prefix to avoid collisions with markdown renderer names:

```svelte

{#snippet html_div({ attributes, children })}

{@render children?.()}

{/snippet}

{#snippet html_a({ attributes, children })}

{@render children?.()}

{/snippet}

```

All HTML snippets share a uniform props interface: `{ attributes?: Record, children?: Snippet }`.

### Custom HTML Tags

You can render arbitrary (non-standard) HTML tags like ``, ``, or any custom element by providing a renderer or snippet for the tag name. The parsing pipeline accepts any tag name โ€” you just need to tell `SvelteMarkdown` how to render it.

**Component renderer approach:**

```svelte

import SvelteMarkdown from '@humanspeak/svelte-markdown'
import ClickButton from './ClickButton.svelte'

const source = '<click>Click Me</click>'
const renderers = { html: { click: ClickButton } }

```

**Snippet override approach:**

```svelte
Click Me'}>
{#snippet html_click({ attributes, children })}
{@render children?.()}
{/snippet}

```

Both approaches work for any tag name. Snippet overrides take precedence over component renderers when both are provided.

## Marked Extensions

Use [marked extensions](https://marked.js.org/using_advanced#extensions) via the `extensions` prop. SvelteMarkdown ships first-class extensions for KaTeX, Mermaid, GitHub-style alerts, and footnotes from the `@humanspeak/svelte-markdown/extensions` subpath โ€” no third-party packages required. Third-party extensions still work too; the component handles registering tokenizers internally and you just provide renderers for the custom token types.

### KaTeX Math Rendering

The package includes built-in `markedKatex` and `KatexRenderer` helpers. Install `katex` as an optional peer dependency and load its CSS:

```bash
npm install katex
```

**Default delimiter set** (mirrors KaTeX's own [`auto-render`](https://katex.org/docs/autorender.html) defaults):

| Delimiter pair | Level | `displayMode` |
| -------------------------------------------------------------- | ------ | ------------- |
| `\(...\)` | inline | `false` |
| `\[...\]` (own-line) | block | `true` |
| `$$...$$` (own-line) | block | `true` |
| `\begin{equation}...\end{equation}` and other AMS environments | block | `true` |

Single-dollar inline (`$x^2$`) is **off** by default โ€” KaTeX itself excludes it from auto-render to avoid currency-string clashes like `$5,000`. Pass `{ singleDollarInline: true }` to enable it; it uses a whitespace-bounded rule so currency strings still won't match.

**Component renderer approach:**

```svelte

import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import { markedKatex, KatexRenderer } from '@humanspeak/svelte-markdown/extensions'

interface KatexRenderers extends Renderers {
inlineKatex: RendererComponent
blockKatex: RendererComponent
}

const renderers: Partial<KatexRenderers> = {
inlineKatex: KatexRenderer,
blockKatex: KatexRenderer
}

```

`KatexRenderer` hardcodes `throwOnError: false` so a single malformed expression renders as a tinted error span instead of throwing โ€” if you need stricter behavior, supply your own component for the `inlineKatex` / `blockKatex` keys.

**Snippet override approach** (no separate component file needed):

```svelte

import SvelteMarkdown from '@humanspeak/svelte-markdown'
import { markedKatex } from '@humanspeak/svelte-markdown/extensions'
import katex from 'katex'

{#snippet inlineKatex(props)}
{@html katex.renderToString(props.text, { throwOnError: false, displayMode: false })}
{/snippet}
{#snippet blockKatex(props)}
{@html katex.renderToString(props.text, { throwOnError: false, displayMode: true })}
{/snippet}

```

### Mermaid Diagrams (Async Rendering)

The package includes built-in `markedMermaid` and `MermaidRenderer` helpers for Mermaid diagram support. Install mermaid as an optional peer dependency:

```bash
npm install mermaid
```

Then use the built-in helpers โ€” no boilerplate needed:

```svelte

import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import { markedMermaid, MermaidRenderer } from '@humanspeak/svelte-markdown/extensions'

// markdown containing fenced mermaid code blocks
let { source } = $props()

interface MermaidRenderers extends Renderers {
mermaid: RendererComponent
}

const renderers: Partial<MermaidRenderers> = {
mermaid: MermaidRenderer
}

```

`markedMermaid()` is a zero-dependency tokenizer that converts ` ```mermaid ` code blocks into custom tokens. `MermaidRenderer` lazy-loads mermaid in the browser, renders SVG asynchronously, and automatically re-renders when dark/light mode changes.

You can also use snippet overrides to wrap `MermaidRenderer` with custom markup:

```svelte

{#snippet mermaid(props)}




{/snippet}

```

Since Mermaid rendering is async, the snippet delegates to `MermaidRenderer` rather than calling `mermaid.render()` directly. This pattern works for any async extension โ€” keep the async logic in a component and use the snippet for layout customization.

### GitHub Alerts

Built-in support for [GitHub-style alerts/admonitions](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts). Five alert types are supported: `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, and `CAUTION`.

```svelte

import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import { markedAlert, AlertRenderer } from '@humanspeak/svelte-markdown/extensions'

const source = `
> [!NOTE]
> Useful information that users should know.

> [!WARNING]
> Urgent info that needs immediate attention.
`

interface AlertRenderers extends Renderers {
alert: RendererComponent
}

const renderers: Partial<AlertRenderers> = {
alert: AlertRenderer
}

```

`AlertRenderer` renders a `

` with a title โ€” no inline styles, so you can theme it with your own CSS. You can also use snippet overrides:

```svelte

{#snippet alert(props)}


{props.alertType}

{props.text}



{/snippet}

```

### Footnotes

Built-in support for footnote references and definitions. Footnote references (`[^id]`) render as superscript links, and definitions (`[^id]: content`) render as a numbered list at the end of the document with back-links.

```svelte

import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import {
markedFootnote,
FootnoteRef,
FootnoteSection
} from '@humanspeak/svelte-markdown/extensions'

const source = `
Here is a statement[^1] with a footnote.

Another claim[^note] that needs a source.

[^1]: This is the first footnote.
[^note]: This is a named footnote.
`

interface FootnoteRenderers extends Renderers {
footnoteRef: RendererComponent
footnoteSection: RendererComponent
}

const renderers: Partial<FootnoteRenderers> = {
footnoteRef: FootnoteRef,
footnoteSection: FootnoteSection
}

```

`FootnoteRef` renders `{id}` and `FootnoteSection` renders an `

    ` with bidirectional links (ref to definition and back). You can also use snippet overrides for custom rendering.

    ### How It Works

    Marked extensions define custom token types with a `name` property (e.g., `inlineKatex`, `blockKatex`, `alert`). When you pass extensions via the `extensions` prop, SvelteMarkdown automatically extracts these token type names and makes them available as both **component renderer keys** and **snippet override names**.

    To find the token type names for any extension, check its source or documentation for the `name` field in its `extensions` array:

    ```js
    // Example: markedKatex (built-in) registers tokens named "inlineKatex" and "blockKatex"
    // โ†’ use renderers={{ inlineKatex: ..., blockKatex: ... }}
    // โ†’ or {#snippet inlineKatex(props)} and {#snippet blockKatex(props)}

    // Example: a custom alert extension registers a token named "alert"
    // โ†’ use renderers={{ alert: AlertComponent }}
    // โ†’ or {#snippet alert(props)}
    ```

    Each snippet/component receives the token's properties as props (e.g., `text`, `displayMode` for KaTeX; `text`, `level` for alerts).

    See the [full documentation](https://markdown.svelte.page/docs/advanced/marked-extensions) and [interactive demo](https://markdown.svelte.page/examples/marked-extensions).

    ### TypeScript

    All snippet prop types are exported for use in external components:

    ```typescript
    import type {
    ParagraphSnippetProps,
    HeadingSnippetProps,
    LinkSnippetProps,
    CodeSnippetProps,
    HtmlSnippetProps,
    SnippetOverrides,
    HtmlSnippetOverrides
    } from '@humanspeak/svelte-markdown'
    ```

    ## Advanced Features

    ### Table Support with Mixed Content

    The package excels at handling complex nested structures and mixed content:

    ```markdown
    | Type | Content |
    | ---------- | --------------------------------------- |
    | Nested |

    **bold** and _italic_
    |
    | Mixed List |

    • Item 1

    • Item 2

    |
    | Code | `inline code` |
    ```

    ### HTML in Markdown

    Seamlessly mix HTML and Markdown:

    ```markdown


    ### This is a Markdown heading inside HTML
    And here's some **bold** text too!

    Click to expand

    - This is a markdown list
    - Inside an HTML details element
    - Supporting **bold** and _italic_ text

    ```

    ## Performance

    ### Intelligent Token Caching

    Parsed tokens are automatically cached using an LRU strategy, providing 50-200x faster re-renders for previously seen content (< 1ms vs 50-200ms). The cache uses FNV-1a hashing keyed on source + options, with LRU eviction (default 50 documents) and TTL expiration (default 5 minutes). No configuration required.

    ```typescript
    import { tokenCache, TokenCache } from '@humanspeak/svelte-markdown'

    // Manual cache management
    tokenCache.clearAllTokens()
    tokenCache.deleteTokens(markdown, options)

    // Custom cache instance
    const myCache = new TokenCache({ maxSize: 100, ttl: 10 * 60 * 1000 })
    ```

    ### Smart Image Lazy Loading

    Images automatically lazy load using native `loading="lazy"` and IntersectionObserver prefetching, with a smooth fade-in animation and error state handling. To disable lazy loading, provide a custom Image renderer:

    ```svelte

    let { href = '', title = undefined, text = '' } = $props()

    {text}
    ```

    ```svelte

    import SvelteMarkdown from '@humanspeak/svelte-markdown'
    import EagerImage from './EagerImage.svelte'

    const renderers = { image: EagerImage }

    ```

    ### LLM Streaming

    For real-time rendering of AI responses from ChatGPT, Claude, Gemini, and other LLMs, enable the `streaming` prop. This uses a smart diff algorithm that re-parses the full source for correctness but only updates changed DOM nodes, keeping render times constant regardless of document size.

    The preferred API is now imperative: bind the component instance and call `writeChunk()` as chunks arrive. This avoids prop reactivity edge cases like identical consecutive string chunks being coalesced.

    ```svelte

    import SvelteMarkdown from '@humanspeak/svelte-markdown'
    import type { StreamingChunk } from '@humanspeak/svelte-markdown'

    let markdown:
    | {
    writeChunk: (chunk: StreamingChunk) => void
    resetStream: (nextSource?: string) => void
    }
    | undefined

    async function streamResponse() {
    const response = await fetch('/api/chat', { method: 'POST', body: '...' })
    const reader = response.body.getReader()
    const decoder = new TextDecoder()

    while (true) {
    const { done, value } = await reader.read()
    if (done) break
    markdown?.writeChunk(decoder.decode(value, { stream: true }))
    }
    }

    ```

    For websocket-style offset patches, pass an object chunk instead:

    ```ts
    markdown?.writeChunk({ value: 'world', offset: 6 })
    ```

    Object chunks overwrite the internal buffer at `offset`. This is overwrite semantics, not insert semantics: the chunk replaces characters starting at that index and preserves any trailing content after the overwritten span.

    If `offset` skips ahead, missing positions are padded with spaces. There is no delete or truncate behavior in offset mode.

    Typical websocket-style usage can arrive out of order:

    ```ts
    markdown?.writeChunk({ value: ' world', offset: 5 })
    markdown?.writeChunk({ value: 'Hello', offset: 0 })
    ```

    The internal buffer converges as later patches fill earlier gaps.

    You can reset the internal streaming buffer at any time:

    ```ts
    markdown?.resetStream('')
    markdown?.resetStream('# Seeded response')
    ```

    The first successful write after a reset locks the stream into one input mode:

    - `string` chunks: append mode
    - `{ value, offset }` chunks: offset mode

    Switching modes before `resetStream()` or a `source` prop reset logs a warning and drops the chunk. Offset chunks must use a non-negative safe integer `offset`.

    Changing the `source` prop also resets the imperative buffer, seeds a new baseline value, and unlocks the input mode.

    Appending directly to `source` is still supported:

    ```svelte

    import SvelteMarkdown from '@humanspeak/svelte-markdown'

    let source = $state('')

    function onChunk(chunk: string) {
    source += chunk
    }

    ```

    **Performance** (measured at 100 characters/sec, character mode):

    | Metric | Standard Mode | Streaming Mode |
    | -------------- | :-----------: | :------------: |
    | Average render | ~3.6ms | ~1.6ms |
    | Peak render | ~21ms | ~10ms |
    | Dropped frames | 0 | 0 |

    When `streaming` is `false` (default), existing behavior is unchanged. The `streaming` prop skips cache lookups (always a miss during streaming) and uses in-place token array mutation so Svelte only re-renders components for tokens that actually changed.

    **Note:** `streaming` is automatically disabled when async extensions (e.g., `markedMermaid`) are used. A console warning is logged in this case.

    See the [full streaming documentation](https://markdown.svelte.page/docs/advanced/llm-streaming) and [interactive demo](https://markdown.svelte.page/examples/llm-streaming).

    ## Available Renderers

    - `text` - Text within other elements
    - `paragraph` - Paragraph (`

    `)
    - `em` - Emphasis (``)
    - `strong` - Strong/bold (``)
    - `hr` - Horizontal rule (`


    `)
    - `blockquote` - Block quote (`
    `)
    - `del` - Deleted/strike-through (``)
    - `link` - Link (``)
    - `image` - Image (``)
    - `table` - Table (`
    `)
    - `tablehead` - Table head (``)
    - `tablebody` - Table body (``)
    - `tablerow` - Table row (``)
    - `tablecell` - Table cell (``/``)
    - `list` - List (`
      `/`
        `)
        - `listitem` - List item (`
      1. `)
        - `heading` - Heading (`

        `-`

        `)
        - `codespan` - Inline code (``)
        - `code` - Block of code (`
        `)
        
        - `html` - HTML node
        - `rawtext` - All other text that is going to be included in an object above

        ### Optional List Renderers

        For fine-grained styling:

        - `orderedlistitem` - Items in ordered lists
        - `unorderedlistitem` - Items in unordered lists

        ### HTML Renderers

        The `html` renderer is special and can be configured separately to handle HTML elements:

        | Element | Description |
        | -------- | -------------------- |
        | `div` | Division element |
        | `span` | Inline container |
        | `table` | HTML table structure |
        | `thead` | Table header group |
        | `tbody` | Table body group |
        | `tr` | Table row |
        | `td` | Table data cell |
        | `th` | Table header cell |
        | `ul` | Unordered list |
        | `ol` | Ordered list |
        | `li` | List item |
        | `code` | Code block |
        | `em` | Emphasized text |
        | `strong` | Strong text |
        | `a` | Anchor/link |
        | `img` | Image |

        You can customize HTML rendering by providing your own components:

        ```typescript
        import type { HtmlRenderers } from '@humanspeak/svelte-markdown'

        const customHtmlRenderers: Partial = {
        div: YourCustomDivComponent,
        span: YourCustomSpanComponent
        }
        ```

        ## Events

        The component emits a `parsed` event when tokens are calculated:

        ```svelte

        import SvelteMarkdown from '@humanspeak/svelte-markdown'

        const handleParsed = (tokens: Token[] | TokensList) => {
        console.log('Parsed tokens:', tokens)
        }

        ```

        ## Props

        | Prop | Type | Description |
        | ---------- | ----------------------- | ------------------------------------------------ |
        | source | `string \| Token[]` | Markdown content or pre-parsed tokens |
        | streaming | `boolean` | Enable incremental rendering for LLM streaming |
        | renderers | `Partial` | Custom component overrides |
        | options | `SvelteMarkdownOptions` | Marked parser configuration |
        | isInline | `boolean` | Toggle inline parsing mode |
        | extensions | `MarkedExtension[]` | Third-party marked extensions (e.g., KaTeX math) |

        ## Security

        This package takes a defense-in-depth approach to security:

        - **Secure HTML parsing** - All HTML is parsed through HTMLParser2's streaming parser rather than `innerHTML`, preventing script injection
        - **XSS protection** - HTML entities are safely handled; malicious markdown injection is neutralized during parsing
        - **Granular HTML control** - Use `allowHtmlOnly()` / `excludeHtmlOnly()` to restrict which HTML tags are rendered (see [Helper utilities](#helper-utilities-for-allowdeny-strategies))
        - **Full HTML lockdown** - Call `buildUnsupportedHTML()` to block all raw HTML rendering
        - **Markdown renderer control** - Use `allowRenderersOnly()` / `excludeRenderersOnly()` to limit which markdown token types are rendered
        - **No built-in sanitizer** - By design, the package does not bundle a sanitizer. Integrate your own (e.g., DOMPurify) if you accept untrusted input

        ## License

        MIT ยฉ [Humanspeak, Inc.](LICENSE)

        ## Credits

        Made with โค๏ธ by [Humanspeak](https://humanspeak.com)