typlete Svelte Themes

Typlete

Typst + Svelte

Typlete

Svelte 5 components and Markdown helpers for rendering Typst as SVG.

Requirements

  • Svelte >= 5.36
  • Svelte async rendering enabled: compilerOptions.experimental.async = true
  • Vite/SvelteKit-compatible asset handling for browser WASM imports
// svelte.config.js
export default {
    compilerOptions: {
        experimental: {
            async: true
        }
    }
};

Install

pnpm add typlete

Svelte usage

<script lang="ts">
    import { TypstInline, TypstBlock } from 'typlete';
</script>

<p>
    Inline formula: <TypstInline source="integral_0^1 x^2 dif x" />
</p>

<TypstBlock source="sum_(i=1)^n i = (n(n+1)) / 2" />

source is Typst math by default. Typlete wraps math input in Typst math delimiters and escapes delimiter-breaking characters ($ and #) before compiling. Do not include outer $...$; pass the formula body.

Raw Typst

Use inputMode="raw" for full Typst markup fragments that should not be wrapped as math. Raw mode passes the source to Typst unchanged, so user-controlled raw input needs separate policy around allowed Typst features and assets.

<TypstBlock inputMode="raw" source={'#rect(radius: 6pt, inset: 10pt)[#strong[Raw Typst]]'} />
<!-- math mode: Typlete wraps source in Typst math -->
<TypstInline source="alpha + beta" />

<!-- raw mode: Typlete passes source to Typst unchanged -->
<TypstInline inputMode="raw" source={'$alpha + beta$'} />

inputMode="markup" is accepted as a compatibility alias for raw.

Props

Typst, TypstInline, and TypstBlock support these common props:

type TypstMode = 'inline' | 'block';
type TypstInputMode = 'math' | 'raw' | 'markup';
type TypstSvgSanitizer = (svg: string) => string;

source?: string;
inputMode?: TypstInputMode;
preamble?: string;
textSize?: string;
pageMargin?: string;
cache?: boolean;
sanitize?: TypstSvgSanitizer;
ariaLabel?: string;
title?: string;
loadingLabel?: string;
throwOnError?: boolean;
errorMode?: 'badge' | 'none';
class?: string;

Typst also accepts mode. TypstInline and TypstBlock set mode automatically.

SSR and browser WASM

During SSR/build, Typlete renders SVG on the server. After hydration, the browser keeps the server-rendered SVG and loads the Typst WASM runtime only when the formula has to be rendered again on the client.

The browser compiler and renderer WASM files are imported as Vite assets. Consumers do not need to copy WASM files manually.

Error behavior

By default, render failures keep the last successful SVG visible and show a small error badge.

<TypstBlock source={formula} errorMode="badge" />
<TypstBlock source={formula} errorMode="none" />

Use throwOnError={true} when server-side render failures should fail the request/build instead of being converted to component state.

Server helper

import { renderTypstSvgServer } from 'typlete/server';

const svg = await renderTypstSvgServer({
    source: 'integral_0^1 x^2 dif x',
    mode: 'block',
    inputMode: 'math',
    preamble: '',
    textSize: '11pt',
    pageMargin: '0pt',
    cache: true
});

Markdown blocks

Typlete only renders namespaced Typlete fences. Normal typst fences stay normal Markdown code blocks, so documentation can show Typst source without triggering rendering.

Raw Typst render block:

```typlete-typst
#set text(size: 12pt)
#rect[hello]
```

Math render block. The content is treated as a formula body; delimiter-breaking $ and # characters are escaped before Typst compiles it:

```typlete-math
sum_(i=1)^n i = (n(n+1)) / 2
```

Accepted render fence names:

typlete -> raw Typst render block
typlete raw -> raw Typst render block
typlete typst -> raw Typst render block
typlete-typst -> raw Typst render block
typlete-raw -> raw Typst render block
typlete math -> math render block
typlete-math -> math render block
typlete-typst-math -> math render block

Typst source code remains source code:

```typst
#set text(size: 12pt)
#rect[hello]
```

For inline formulas in MDsveX, use the Svelte component directly. The same math escaping applies:

The value is <TypstInline source="alpha + beta" />.

Markdown transform

import { transformTypleteMarkdown } from 'typlete/markdown';

const output = await transformTypleteMarkdown(markdown, {
    output: 'component'
});

Output modes:

type TypleteMarkdownOutput = 'component' | 'html' | 'markdown-image' | 'asset';

Component output

Best for MDsveX/Svelte-aware Markdown pipelines.

await transformTypleteMarkdown(markdown, {
    output: 'component'
});

Produces Svelte component tags:

<TypstBlock source={'#rect[hello]'} inputMode="raw" />
<TypstBlock source={'alpha + beta'} />

HTML output

Best for renderers that accept raw HTML/SVG.

await transformTypleteMarkdown(markdown, {
    output: 'html'
});

This renders SVG immediately and inserts it into the Markdown output.

Markdown image output

Best for renderers that do not support raw HTML but accept Markdown images.

await transformTypleteMarkdown(markdown, {
    output: 'markdown-image'
});

This renders SVG and inserts it as a data:image/svg+xml Markdown image.

Asset output

Best for static sites.

await transformTypleteMarkdown(markdown, {
    output: 'asset',
    assetDir: 'static/typlete',
    assetBaseUrl: '/typlete'
});

This writes SVG files to assetDir and inserts normal Markdown image links.

MDsveX preprocessor

Run the Typlete preprocessor before MDsveX so namespaced Typlete render fences are converted to Svelte component tags before MDsveX compiles the document.

// svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import { mdsvex } from 'mdsvex';
import { createTypstMdsvexPreprocessor } from 'typlete/markdown';

const config = {
    extensions: ['.svelte', '.svx', '.md'],
    preprocess: [
        createTypstMdsvexPreprocessor({
            output: 'component'
        }),
        mdsvex({
            extensions: ['.svx', '.md']
        })
    ],
    compilerOptions: {
        experimental: {
            async: true
        }
    },
    kit: {
        adapter: adapter()
    }
};

export default config;

For output: 'component', the preprocessor injects this import when a document contains a rendered Typst fence:

<script lang="ts">
    import { TypstInline, TypstBlock } from 'typlete';
</script>

Disable injection only if another MDsveX hook already imports the components:

createTypstMdsvexPreprocessor({
    output: 'component',
    injectComponentImports: false
});

Rendering options for Markdown

Markdown helpers accept the same render options as component/server rendering:

await transformTypleteMarkdown(markdown, {
    output: 'asset',
    assetDir: 'static/typlete',
    assetBaseUrl: '/typlete',
    preamble: '',
    textSize: '11pt',
    pageMargin: '0pt',
    cache: true
});

Sanitizing SVG

Typlete inserts compiler-produced SVG inline. By default it strips embedded SVG <script> tags and common script-like SVG attributes from that output. This is output-level defense-in-depth, not a sandbox for arbitrary raw Typst input or external assets. For stricter SVG sanitizing, pass a sanitizer.

DOMPurify is optional:

import { createDomPurifySvgSanitizer } from 'typlete/sanitizers/dompurify';

const sanitize = await createDomPurifySvgSanitizer();

Server-side DOMPurify requires both dompurify and jsdom:

import { createServerDomPurifySvgSanitizer } from 'typlete/server/dompurify';

const sanitize = await createServerDomPurifySvgSanitizer();

Limitations

  • Component SSR depends on Svelte experimental async rendering.
  • Plain typst fences are never render instructions; use typlete-typst, typlete-raw, or typlete-math when Markdown should render Typst.
  • Math mode and typlete-math escape $ and # before wrapping the source in Typst math delimiters. Raw mode and preamble are passed to Typst unchanged.
  • Server rendering temporarily guards Typst runtime fetches so SvelteKit does not track external runtime fetches during SSR.
  • html, markdown-image, and asset output modes pre-render SVG immediately and are not reactive on the client.

Top categories

Loading Svelte Themes