Marked Extensions
@humanspeak/svelte-markdown supports any marked extension that adds custom token types. Pass extensions via the extensions prop and provide renderers for the custom token types — either 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, marked-katex-extension 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:
// marked-katex-extension → 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 }}// marked-katex-extension → 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
This example uses marked-katex-extension to add $...$ (inline) and $$...$$ (block) math syntax.
1. Install Dependencies
npm install marked-katex-extension katexnpm install marked-katex-extension katex2. Create a Svelte Renderer
The marked-katex-extension produces tokens with text and displayMode properties. Create a Svelte component that calls katex.renderToString():
<!-- KatexRenderer.svelte -->
<script lang="ts">
import katex from 'katex'
interface Props {
text: string
displayMode?: boolean
}
const { text, displayMode = false }: Props = $props()
const html = $derived(
katex.renderToString(text, { throwOnError: false, displayMode })
)
</script>
{@html html}<!-- KatexRenderer.svelte -->
<script lang="ts">
import katex from 'katex'
interface Props {
text: string
displayMode?: boolean
}
const { text, displayMode = false }: Props = $props()
const html = $derived(
katex.renderToString(text, { throwOnError: false, displayMode })
)
</script>
{@html html}This single component handles both inlineKatex (displayMode = false) and blockKatex (displayMode = true) tokens.
3. Wire Up with Component Renderers
Pass the extension via the extensions prop and map token types to your component via renderers:
<script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import markedKatex from 'marked-katex-extension'
import KatexRenderer from './KatexRenderer.svelte'
interface KatexRenderers extends Renderers {
inlineKatex: RendererComponent
blockKatex: RendererComponent
}
const renderers: Partial<KatexRenderers> = {
inlineKatex: KatexRenderer,
blockKatex: KatexRenderer
}
</script>
<SvelteMarkdown
source={markdown}
extensions={[markedKatex({ throwOnError: false })]}
{renderers}
/><script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import markedKatex from 'marked-katex-extension'
import KatexRenderer from './KatexRenderer.svelte'
interface KatexRenderers extends Renderers {
inlineKatex: RendererComponent
blockKatex: RendererComponent
}
const renderers: Partial<KatexRenderers> = {
inlineKatex: KatexRenderer,
blockKatex: KatexRenderer
}
</script>
<SvelteMarkdown
source={markdown}
extensions={[markedKatex({ throwOnError: false })]}
{renderers}
/>4. 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 katex from 'katex'
import markedKatex from 'marked-katex-extension'
</script>
<svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>
<SvelteMarkdown
source={markdown}
extensions={[markedKatex({ throwOnError: false })]}
>
{#snippet inlineKatex(props)}
{@html katex.renderToString(props.text, { displayMode: false })}
{/snippet}
{#snippet blockKatex(props)}
{@html katex.renderToString(props.text, { displayMode: true })}
{/snippet}
</SvelteMarkdown><script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown'
import katex from 'katex'
import markedKatex from 'marked-katex-extension'
</script>
<svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>
<SvelteMarkdown
source={markdown}
extensions={[markedKatex({ throwOnError: false })]}
>
{#snippet inlineKatex(props)}
{@html katex.renderToString(props.text, { displayMode: false })}
{/snippet}
{#snippet blockKatex(props)}
{@html katex.renderToString(props.text, { displayMode: true })}
{/snippet}
</SvelteMarkdown>5. Include KaTeX CSS
KaTeX requires its stylesheet for proper math formatting:
<svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head><svelte:head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/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 from 'marked-katex-extension'
import KatexRenderer from './KatexRenderer.svelte'
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/[email protected]/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>
<SvelteMarkdown
{source}
extensions={[markedKatex({ throwOnError: false })]}
{renderers}
/><script lang="ts">
import SvelteMarkdown from '@humanspeak/svelte-markdown'
import type { RendererComponent, Renderers } from '@humanspeak/svelte-markdown'
import markedKatex from 'marked-katex-extension'
import KatexRenderer from './KatexRenderer.svelte'
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/[email protected]/dist/katex.min.css"
crossorigin="anonymous"
/>
</svelte:head>
<SvelteMarkdown
{source}
extensions={[markedKatex({ throwOnError: false })]}
{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
- 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:
marked-katex-extension,marked-mathjax - Alerts/Admonitions:
marked-alert, custom GFM-style alerts - Diagrams: Extensions wrapping Mermaid, PlantUML, etc.
- Syntax highlighting:
marked-highlight - Custom containers:
marked-custom-heading-id,marked-footnote
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