svelte-markdown Svelte Themes

Svelte Markdown

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

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

Features

  • ๐Ÿš€ Full markdown syntax support through Marked
  • ๐Ÿ’ช Complete TypeScript support with strict typing
  • ๐ŸŽจ Customizable component rendering system
  • ๐Ÿ”’ Secure HTML parsing via HTMLParser2
  • ๐ŸŽฏ GitHub-style slug generation for headers
  • โ™ฟ WCAG 2.1 accessibility compliance
  • ๐Ÿงช Comprehensive test coverage (vitest and playwright)
  • ๐Ÿ”„ Svelte 5 runes compatibility
  • ๐Ÿ›ก๏ธ XSS protection and sanitization
  • ๐ŸŽจ Custom Marked extensions support (e.g., GitHub-style alerts)
  • ๐Ÿ” Improved attribute handling and component isolation
  • ๐Ÿ“ฆ Enhanced token cleanup and nested content support

Recent Updates

New Features

  • Improved HTML attribute isolation for nested components
  • Enhanced token cleanup for better nested content handling
  • Added proper attribute inheritance control
  • Implemented strict debugging checks in CI/CD pipeline

Testing Improvements

  • Enhanced Playwright E2E test coverage
  • Added comprehensive tests for custom extensions
  • Improved test reliability with proper component mounting checks
  • Added specific test cases for nested component scenarios
  • Note: Performance tests use a higher threshold for Firefox due to slower execution in CI environments. See tests/performance.test.ts for details.

CI/CD Enhancements

  • Added automated debugging statement detection
  • Improved release workflow with GPG signing
  • Enhanced PR validation and automated version bumping
  • Added manual workflow triggers for better release control
  • Implemented monthly cache cleanup

Installation

npm i -S @humanspeak/svelte-markdown

Or with your preferred package manager:

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

External Dependencies

This package carefully selects its dependencies to provide a robust and maintainable solution:

Core Dependencies

  • marked

    • Industry-standard markdown parser
    • Battle-tested in production
    • Extensive security features
  • github-slugger

    • GitHub-style heading ID generation
    • Unicode support
    • Collision handling
  • htmlparser2

    • High-performance HTML parsing
    • Streaming capabilities
    • Security-focused design

Basic Usage

<script lang="ts">
    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
`
</script>

<SvelteMarkdown {source} />

TypeScript Support

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

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

Exports for programmatic overrides

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

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):

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:

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:

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):

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:

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

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

Exclude just link; keep others as defaults:

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

const md = excludeRenderersOnly(['link'])

Disable all markdown renderers (except html) quickly:

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

const md = buildUnsupportedRenderers()

Combine HTML and Markdown helpers

You can combine both maps in renderers for SvelteMarkdown.

<script lang="ts">
    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).`
</script>

<SvelteMarkdown {source} {renderers} />

Custom Renderer Example

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

<script lang="ts">
    import type { Snippet } from 'svelte'

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

    const { href = '', title = '', children }: Props = $props()
</script>

<a {href} {title} class="custom-link">
    {@render children?.()}
</a>

If you would like to extend other renderers please take a look inside the renderers folder for the default implentation of them. If you would like feature additions please feel free to open an issue!

Advanced Features

Table Support with Mixed Content

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

| Type       | Content                                 |
| ---------- | --------------------------------------- |
| Nested     | <div>**bold** and _italic_</div>        |
| Mixed List | <ul><li>Item 1</li><li>Item 2</li></ul> |
| Code       | <code>`inline code`</code>              |

HTML in Markdown

Seamlessly mix HTML and Markdown:

<div style="color: blue">
  ### This is a Markdown heading inside HTML
  And here's some **bold** text too!
</div>

<details>
<summary>Click to expand</summary>

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

</details>

Available Renderers

  • text - Text within other elements
  • paragraph - Paragraph (<p>)
  • em - Emphasis (<em>)
  • strong - Strong/bold (<strong>)
  • hr - Horizontal rule (<hr>)
  • blockquote - Block quote (<blockquote>)
  • del - Deleted/strike-through (<del>)
  • link - Link (<a>)
  • image - Image (<img>)
  • table - Table (<table>)
  • tablehead - Table head (<thead>)
  • tablebody - Table body (<tbody>)
  • tablerow - Table row (<tr>)
  • tablecell - Table cell (<td>/<th>)
  • list - List (<ul>/<ol>)
  • listitem - List item (<li>)
  • heading - Heading (<h1>-<h6>)
  • codespan - Inline code (<code>)
  • code - Block of code (<pre><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:

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

const customHtmlRenderers: Partial<HtmlRenderers> = {
    div: YourCustomDivComponent,
    span: YourCustomSpanComponent
}

Events

The component emits a parsed event when tokens are calculated:

<script lang="ts">
    import SvelteMarkdown from '@humanspeak/svelte-markdown'

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

<SvelteMarkdown {source} parsed={handleParsed} />

Props

Prop Type Description
source string | Token[] Markdown content or pre-parsed tokens
renderers Partial<Renderers> Custom component overrides
options SvelteMarkdownOptions Marked parser configuration
isInline boolean Toggle inline parsing mode

Security

The package includes several security features:

  • XSS protection through HTML sanitization
  • Secure HTML parsing with HTMLParser2
  • Safe handling of HTML entities
  • Protection against malicious markdown injection

License

MIT ยฉ Humanspeak, Inc.

Credits

Made with โ™ฅ by Humanspeak

Top categories

Loading Svelte Themes