<!-- Source: https://markdown.svelte.page/examples/mermaid -->

# Mermaid Diagrams

> Render live Mermaid diagrams in @humanspeak/svelte-markdown with markedMermaid, using a MermaidRenderer component or an inline Svelte 5 snippet.

**Source:** [https://markdown.svelte.page/examples/mermaid](https://markdown.svelte.page/examples/mermaid)

**Markdown mirror:** [https://markdown.svelte.page/examples/mermaid.md](https://markdown.svelte.page/examples/mermaid.md)

---

This mirror preserves the prose, implementation notes, and runnable Svelte source behind the live example page.

## FIG-001: component renderer.

Pass the built-in `MermaidRenderer` through the `renderers` prop. Handles async rendering, loading + error states, and dark-mode reactivity with no custom logic on your end.

**Metadata:** tag: `COMPONENT` | override: `component`

### Notes

- `markedMermaid()` registers an async `walkTokens` extension that turns `\`\`\`mermaid` fences into renderable tokens.
- Built-in `MermaidRenderer` handles flowcharts, sequence diagrams, class diagrams, state diagrams, gantt charts.
- Loading + error states + dark-mode theme switching are all handled — zero custom logic on your end.

### Source

#### ComponentRendered.svelte

Source file: [src/lib/examples/mermaid/demos/ComponentRendered.svelte](https://github.com/humanspeak/svelte-markdown/blob/main/docs/src/lib/examples/mermaid/demos/ComponentRendered.svelte)

````svelte
<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'
    import { DemoSplitV2 } from '@humanspeak/docs-kit'

    const defaultMarkdown = `## Mermaid Diagrams

### Flowchart

\`\`\`mermaid
graph TD
    A[Start] --> B{Decision}
    B -->|Yes| C[Action 1]
    B -->|No| D[Action 2]
    C --> E[End]
    D --> E
\`\`\`

### Sequence Diagram

\`\`\`mermaid
sequenceDiagram
    participant A as Alice
    participant B as Bob
    A->>B: Hello Bob!
    B->>A: Hi Alice!
    A->>B: How are you?
    B->>A: Great, thanks!
\`\`\`

### Mixed Content

Regular markdown works alongside diagrams: **bold**, *italic*, and \`inline code\`.

#### Class Diagram

\`\`\`mermaid
classDiagram
    class Animal {
        +String name
        +int age
        +makeSound()
    }
    class Dog {
        +fetch()
    }
    class Cat {
        +purr()
    }
    Animal <|-- Dog
    Animal <|-- Cat
\`\`\``

    // The markedMermaid extension parses `mermaid` code fences into
    // `mermaid` tokens; the MermaidRenderer component handles async
    // rendering, loading + error states, and dark-mode reactivity.
    interface MermaidRenderers extends Renderers {
        mermaid: RendererComponent
    }
    const renderers: Partial<MermaidRenderers> = {
        mermaid: MermaidRenderer
    }

    // Debounced live editor: `input` tracks the textarea verbatim,
    // `source` is what SvelteMarkdown actually renders. 500ms keeps the
    // mermaid renderer from thrashing during fast typing.
    let input = $state(defaultMarkdown)
    let source = $state(defaultMarkdown)
    let debounceTimer = $state<ReturnType<typeof setTimeout> | undefined>(undefined)

    function handleInput(event: Event) {
        const target = event.target as HTMLTextAreaElement
        input = target.value
        if (debounceTimer) clearTimeout(debounceTimer)
        debounceTimer = setTimeout(() => {
            source = input
        }, 500)
    }
</script>

<DemoSplitV2 leftLabel="EDITOR" leftTone="markdown" rightLabel="PREVIEW" rightTone="diagrams">
    {#snippet left()}
        <textarea
            value={input}
            oninput={handleInput}
            class="md-editor"
            spellcheck="false"
            placeholder="Type markdown with ```mermaid code blocks..."></textarea>
    {/snippet}
    {#snippet right()}
        <div class="md-preview prose prose-sm dark:prose-invert max-w-none">
            <SvelteMarkdown {source} extensions={[markedMermaid()]} {renderers} />
        </div>
    {/snippet}
</DemoSplitV2>

<style>
    .md-editor {
        width: 100%;
        height: 100%;
        min-height: 360px;
        resize: none;
        border: 0;
        outline: none;
        background: transparent;
        color: var(--brut-ink, var(--foreground, inherit));
        font-family: 'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, monospace;
        font-size: 12.5px;
        line-height: 1.7;
        padding: 0;
    }
    .md-preview {
        font-family: 'Inter Variable', 'Inter', system-ui, sans-serif;
        color: var(--brut-ink-2, var(--muted-foreground, inherit));
    }
    .md-preview :global(h1),
    .md-preview :global(h2),
    .md-preview :global(h3),
    .md-preview :global(h4) {
        color: var(--brut-ink, var(--foreground, inherit));
        letter-spacing: -0.02em;
    }
    .md-preview :global(code) {
        background: var(--brut-bg-2, rgba(127, 127, 127, 0.08));
        border: 1px solid var(--brut-rule, rgba(127, 127, 127, 0.18));
        padding: 0 4px;
        font-size: 12px;
        font-family: 'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, monospace;
    }
    .md-preview :global(blockquote) {
        border-left: 2px solid var(--brut-accent);
        padding-left: 12px;
        font-style: italic;
        color: var(--brut-ink-2);
    }
</style>
````

## FIG-002: snippet wrapped.

Wrap each diagram in extra chrome via `{#snippet mermaid}` — figure + caption, custom container, anything you want. Delegates the async rendering itself to MermaidRenderer.

**Metadata:** tag: `SNIPPET` | override: `snippet wrap`

### Notes

- `{#snippet mermaid({ text })}` lets the page own the chrome around each diagram — figure + caption, custom container, anything.
- The async rendering itself still delegates to `MermaidRenderer` — you only own the wrapper.
- Pairs nicely with `markedMermaid()` — extension parses the fences, snippet shapes the output.

### Source

#### SnippetRendered.svelte

Source file: [src/lib/examples/mermaid/demos/SnippetRendered.svelte](https://github.com/humanspeak/svelte-markdown/blob/main/docs/src/lib/examples/mermaid/demos/SnippetRendered.svelte)

````svelte
<script lang="ts">
    import SvelteMarkdown from '@humanspeak/svelte-markdown'
    import { markedMermaid, MermaidRenderer } from '@humanspeak/svelte-markdown/extensions'
    import { DemoSplitV2 } from '@humanspeak/docs-kit'

    const defaultMarkdown = `## Mermaid Diagrams

### Flowchart

\`\`\`mermaid
graph TD
    A[Start] --> B{Decision}
    B -->|Yes| C[Action 1]
    B -->|No| D[Action 2]
    C --> E[End]
    D --> E
\`\`\`

### Sequence Diagram

\`\`\`mermaid
sequenceDiagram
    participant A as Alice
    participant B as Bob
    A->>B: Hello Bob!
    B->>A: Hi Alice!
    A->>B: How are you?
    B->>A: Great, thanks!
\`\`\`

### Mixed Content

Regular markdown works alongside diagrams: **bold**, *italic*, and \`inline code\`.

#### Class Diagram

\`\`\`mermaid
classDiagram
    class Animal {
        +String name
        +int age
        +makeSound()
    }
    class Dog {
        +fetch()
    }
    class Cat {
        +purr()
    }
    Animal <|-- Dog
    Animal <|-- Cat
\`\`\``

    // Debounced live editor — same pattern as the component variant.
    let input = $state(defaultMarkdown)
    let source = $state(defaultMarkdown)
    let debounceTimer = $state<ReturnType<typeof setTimeout> | undefined>(undefined)

    function handleInput(event: Event) {
        const target = event.target as HTMLTextAreaElement
        input = target.value
        if (debounceTimer) clearTimeout(debounceTimer)
        debounceTimer = setTimeout(() => {
            source = input
        }, 500)
    }
</script>

<DemoSplitV2 leftLabel="EDITOR" leftTone="markdown" rightLabel="PREVIEW" rightTone="wrapped">
    {#snippet left()}
        <textarea
            value={input}
            oninput={handleInput}
            class="md-editor"
            spellcheck="false"
            placeholder="Type markdown with ```mermaid code blocks..."></textarea>
    {/snippet}
    {#snippet right()}
        <div class="md-preview prose prose-sm dark:prose-invert max-w-none">
            <SvelteMarkdown {source} extensions={[markedMermaid()]}>
                {#snippet mermaid(props: { text: string })}
                    <!-- Inline snippet wraps each Mermaid diagram with a
                         labelled caption strip. Async rendering still
                         delegates to MermaidRenderer because the snippet
                         API doesn't await child mounts. -->
                    <figure class="md-diagram">
                        <figcaption class="md-diagram-cap">Mermaid · rendered</figcaption>
                        <MermaidRenderer text={props.text} />
                    </figure>
                {/snippet}
            </SvelteMarkdown>
        </div>
    {/snippet}
</DemoSplitV2>

<style>
    .md-editor {
        width: 100%;
        height: 100%;
        min-height: 360px;
        resize: none;
        border: 0;
        outline: none;
        background: transparent;
        color: var(--brut-ink, var(--foreground, inherit));
        font-family: 'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, monospace;
        font-size: 12.5px;
        line-height: 1.7;
        padding: 0;
    }
    .md-preview {
        font-family: 'Inter Variable', 'Inter', system-ui, sans-serif;
        color: var(--brut-ink-2, var(--muted-foreground, inherit));
    }
    .md-preview :global(h1),
    .md-preview :global(h2),
    .md-preview :global(h3),
    .md-preview :global(h4) {
        color: var(--brut-ink, var(--foreground, inherit));
        letter-spacing: -0.02em;
    }
    .md-preview :global(code) {
        background: var(--brut-bg-2, rgba(127, 127, 127, 0.08));
        border: 1px solid var(--brut-rule, rgba(127, 127, 127, 0.18));
        padding: 0 4px;
        font-size: 12px;
        font-family: 'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, monospace;
    }
    .md-preview :global(blockquote) {
        border-left: 2px solid var(--brut-accent);
        padding-left: 12px;
        font-style: italic;
        color: var(--brut-ink-2);
    }
    :global(.md-diagram) {
        margin: 16px 0;
        padding: 12px;
        border: 1px solid var(--brut-rule);
        background: var(--brut-bg-2);
    }
    :global(.md-diagram-cap) {
        display: inline-block;
        font-family: 'JetBrains Mono Variable', 'JetBrains Mono', ui-monospace, monospace;
        font-size: 10.5px;
        letter-spacing: 0.14em;
        text-transform: uppercase;
        color: var(--brut-accent);
        margin-bottom: 8px;
    }
</style>
````
