Content

MarkdownRenderer

Renders markdown text with KaTeX math support and syntax highlighting. Supports inline and block math expressions.

Props

PropTypeDefaultDescription
textstring—Markdown text to render

Import

import { MarkdownRenderer } from '@the-vcsi/scrolly-kit';

Usage

<script>
  import { MarkdownRenderer } from '@the-vcsi/scrolly-kit';
</script>

<MarkdownRenderer text="## Hello **World**" />

<!-- With math -->
<MarkdownRenderer text="The equation $E = mc^2$ shows..." />

Full Source

💡 Components rely on --vcsi-* tokens from tokens.css. You'd need to either need to @import '@the-vcsi/scrolly-kit/styles/tokens.css'; to access the CSS variables or define equivalent variables in your app.css. We also are using types here to provide hints when users are using the components in their project.

<!--
@component
Markdown renderer with KaTeX math and syntax highlighting.

Supports GitHub Flavored Markdown, LaTeX math ($...$, $$...$$),
and code blocks with syntax highlighting.

## Props
- `text` - Markdown string to render

## Usage
```svelte
<MarkdownRenderer text="## Hello\n\nThis is **bold** and $E = mc^2$" />
```

## Supported Features
- GFM (tables, strikethrough, autolinks)
- KaTeX math (inline and block)
- Code highlighting (JS, CSS, R, HTML)
-->
<script lang="ts">
    import Markdown from 'svelte-exmarkdown';
    import { gfmPlugin } from 'svelte-exmarkdown/gfm';
    import 'katex/dist/katex.min.css';
    import rehypeKatex from 'rehype-katex';
    import remarkMath from 'remark-math';
    import rehypeRaw from 'rehype-raw';
    import rehypeHighlight from 'rehype-highlight';
    import rehypeHighlightCodeLines from 'rehype-highlight-code-lines';
    import 'highlight.js/styles/github.css';
    import { base } from '$app/paths';

    import css from 'highlight.js/lib/languages/css';
    import xml from 'highlight.js/lib/languages/xml';
    import R from 'highlight.js/lib/languages/r';
    import JS from 'highlight.js/lib/languages/javascript';
    import TS from 'highlight.js/lib/languages/typescript';
    import bash from 'highlight.js/lib/languages/bash';

    interface Props {
        text: string;
    }

    let { text }: Props = $props();

    // Using 'as any' due to complex plugin typing in svelte-exmarkdown
    const plugins = [
        gfmPlugin(),
        {
            remarkPlugin: [remarkMath],
            rehypePlugin: [rehypeKatex]
        },
        {
            rehypePlugin: [rehypeRaw]
        },
        {
            rehypePlugin: [
                rehypeHighlight,
                {
                    ignoreMissing: true,
                    languages: {
                        css,
                        html: xml,
                        xml,
                        r: R,
                        svelte: xml,
                        js: JS,
                        typescript: TS,
                        ts: TS,
                        bash,
                        shell: bash
                    }
                }
            ]
        },
        {
            rehypePlugin: [
                rehypeHighlightCodeLines,
                {
                    showLineNumbers: true
                }
            ]
        }
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ] as any[];

    function processContent(content: string): string {
        if (!content) return "";

        // Remove footnote markers
        content = content.replace(/\[\^(\d+)\]/g, '');

        // Preserve code blocks (markdown fences and <pre> tags), remove leading whitespace from non-code
        const parts = content.split(/(```[\s\S]*?```|<pre[\s\S]*?<\/pre>)/);
        content = parts.map((part: string, i: number) =>
            i % 2 === 0 ? part.replace(/^[ \t]+/gm, '') : part
        ).join('');

        // Fix base path for links/images
        content = content.replace(/(src|href)="\/([^"]*?)"/g, `$1="${base}/$2"`);

        return content;
    }
</script>

<Markdown md={processContent(text)} {plugins} />