Marked Extensions
@humanspeak/svelte-markdown ships first-class extensions for KaTeX, Mermaid, GitHub-style alerts, and footnotes from the @humanspeak/svelte-markdown/extensions subpath. Any third-party marked extension that adds custom token types also works — pass it via the extensions prop and provide renderers for the custom token types as component renderers or snippet overrides.
How It Works
Marked extensions define custom token types, each with a name property (e.g., inlineKatex, blockKatex, alert). When you pass extensions via the extensions prop, SvelteMarkdown:
- Registers the extension’s tokenizers internally so the lexer produces the custom tokens
- Extracts the token type
names from the extensions array - Makes those names available as both component renderer keys (
renderers={{ inlineKatex: ... }}) and snippet override names ({#snippet inlineKatex(props)})
Each snippet or component receives the token’s own properties as props. For example, the built-in markedKatex produces tokens with text and displayMode, so your renderer gets { text: string, displayMode: boolean }.
Finding Token Type Names
To discover the snippet/renderer names for any extension, check the name field in its extensions array:
// Built-in markedKatex → tokens named "inlineKatex" and "blockKatex"
// → {#snippet inlineKatex(props)} and {#snippet blockKatex(props)}
// → or renderers={{ inlineKatex: ..., blockKatex: ... }}
// A custom alert extension → token named "alert"
// → {#snippet alert(props)}
// → or renderers={{ alert: AlertComponent }}// Built-in markedKatex → tokens named "inlineKatex" and "blockKatex"
// → {#snippet inlineKatex(props)} and {#snippet blockKatex(props)}
// → or renderers={{ inlineKatex: ..., blockKatex: ... }}
// A custom alert extension → token named "alert"
// → {#snippet alert(props)}
// → or renderers={{ alert: AlertComponent }}You can also inspect the tokens at runtime using the parsed callback:
<SvelteMarkdown {source} {extensions} parsed={(tokens) => console.log(tokens)} /><SvelteMarkdown {source} {extensions} parsed={(tokens) => console.log(tokens)} />Step-by-Step: KaTeX Math Rendering
The package includes built-in markedKatex and KatexRenderer helpers exported from @humanspeak/svelte-markdown/extensions. The default delimiter set mirrors KaTeX’s own auto-render extension:
| 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.
1. Install KaTeX
katex is an optional peer dependency — install it yourself:
npm install katexnpm install katex2. Wire Up with Component Renderers
Pass the extension via the extensions prop and map token types to KatexRenderer:
<script lang="ts">
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
}
</script>
<SvelteMarkdown
source={markdown}
extensions={[markedKatex()]}
{renderers}
/><script lang="ts">
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
}
</script>
<SvelteMarkdown
source={markdown}
extensions={[markedKatex()]}
{renderers}
/>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.
3. Alternative: Snippet Overrides
Instead of component renderers, you can use snippet overrides for inline rendering:
<script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown'
import { markedKatex } from '@humanspeak/svelte-markdown/extensions'
import katex from 'katex'
</script>
<svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>
<SvelteMarkdown
source={markdown}
extensions={[markedKatex()]}
>
{#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}
</SvelteMarkdown><script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown'
import { markedKatex } from '@humanspeak/svelte-markdown/extensions'
import katex from 'katex'
</script>
<svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>
<SvelteMarkdown
source={markdown}
extensions={[markedKatex()]}
>
{#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}
</SvelteMarkdown>4. Include KaTeX CSS
KaTeX requires its stylesheet for proper math formatting:
<svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head><svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>Complete Example
Putting it all together with component renderers:
<script lang="ts">
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
}
const source = `
# Euler's Identity
The equation \\(e^{i\\pi} + 1 = 0\\) is considered the most beautiful in mathematics.
\\[
e^{i\\pi} + 1 = 0
\\]
`
</script>
<svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>
<SvelteMarkdown
{source}
extensions={[markedKatex()]}
{renderers}
/><script lang="ts">
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
}
const source = `
# Euler's Identity
The equation \\(e^{i\\pi} + 1 = 0\\) is considered the most beautiful in mathematics.
\\[
e^{i\\pi} + 1 = 0
\\]
`
</script>
<svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.45/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>
<SvelteMarkdown
{source}
extensions={[markedKatex()]}
{renderers}
/>Step-by-Step: Mermaid Diagrams
Unlike KaTeX (synchronous rendering via katex.renderToString()), Mermaid is async and browser-only — it requires the DOM and uses await mermaid.render(). The package provides built-in markedMermaid and MermaidRenderer helpers so you don’t need to write boilerplate.
1. Install Mermaid
Mermaid is an optional peer dependency — install it yourself:
npm install mermaidnpm install mermaid2. Wire Up with Built-in Helpers
The package exports markedMermaid() (a zero-dep tokenizer for ```mermaid code blocks) and MermaidRenderer (a Svelte component that lazy-loads mermaid and renders SVG diagrams with dark mode support):
<script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import { markedMermaid, MermaidRenderer } from '@humanspeak/svelte-markdown/extensions'
const markdown = `
\`\`\`mermaid
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Action 1]
B -->|No| D[Action 2]
\`\`\`
`
interface MermaidRenderers extends Renderers {
mermaid: RendererComponent
}
const renderers: Partial<MermaidRenderers> = {
mermaid: MermaidRenderer
}
</script>
<SvelteMarkdown
source={markdown}
extensions={[markedMermaid()]}
{renderers}
/><script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import { markedMermaid, MermaidRenderer } from '@humanspeak/svelte-markdown/extensions'
const markdown = `
\`\`\`mermaid
graph TD
A[Start] --> B{Decision}
B -->|Yes| C[Action 1]
B -->|No| D[Action 2]
\`\`\`
`
interface MermaidRenderers extends Renderers {
mermaid: RendererComponent
}
const renderers: Partial<MermaidRenderers> = {
mermaid: MermaidRenderer
}
</script>
<SvelteMarkdown
source={markdown}
extensions={[markedMermaid()]}
{renderers}
/>The built-in MermaidRenderer handles:
- Dynamic import:
import('mermaid')inonMountensures it only loads in the browser - Async rendering:
mermaid.render()returns a promise with the SVG string - Loading/error states: Handles the async lifecycle gracefully
- Unique IDs:
crypto.randomUUID()prevents collisions when multiple diagrams render - Theme reactivity: A
MutationObserverwatches the<html>class for dark/light changes and re-renders with the correct Mermaid theme via per-diagram directives
Snippet Overrides for Async Extensions
You can also use snippet overrides to wrap MermaidRenderer with custom markup — extra classes, wrapper divs, or surrounding content:
<SvelteMarkdown source={markdown} extensions={[markedMermaid()]}>
{#snippet mermaid(props)}
<div class="my-diagram-wrapper">
<MermaidRenderer text={props.text} />
</div>
{/snippet}
</SvelteMarkdown><SvelteMarkdown source={markdown} extensions={[markedMermaid()]}>
{#snippet mermaid(props)}
<div class="my-diagram-wrapper">
<MermaidRenderer text={props.text} />
</div>
{/snippet}
</SvelteMarkdown>Since snippets run synchronously during render, they delegate the async work 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.
General Pattern
The same pattern works for any marked extension:
- Install the extension package (or the optional peer for built-ins like
katex/mermaid) - Discover the token type names — check the extension’s
extensions[].namefields, or use theparsedcallback to inspect tokens - Pass the extension via the
extensionsprop - Render the custom tokens — either map the token type name(s) to Svelte components via
renderers, or use inline{#snippet tokenName(props)}overrides - Include any required CSS or external resources
The token type name is the key that connects everything: it’s what the extension calls the token, what you use as the renderers key, and what you use as the snippet name.
Extension Ecosystem
The marked ecosystem includes extensions for:
- Math:
markedKatex(built-in),marked-mathjax - Alerts/Admonitions:
markedAlert(built-in), custom GFM-style alerts - Diagrams:
markedMermaid(built-in), wrappers around PlantUML, etc. - Syntax highlighting:
marked-highlight - Custom containers:
marked-custom-heading-id,markedFootnote(built-in)
Any extension that adds custom token types can be integrated using the pattern above.
Related
- KaTeX Interactive Demo — try KaTeX math rendering live with both component and snippet approaches
- Mermaid Interactive Demo — try async Mermaid diagram rendering with a custom marked extension
- Custom Renderers — override built-in markdown renderers
- Types & Exports —
RendererComponent,Rendererstype definitions