introduction #
mdz is a strict markdown dialect built for streaming, Svelte authoring, docs websites, and untrusted content. The same grammar serves multiple use cases:
- streaming output from LLMs and other live sources
- authoring with Svelte components, with an optional build-time preprocessor that compiles static usage to plain Svelte markup
- rendering TSDoc/JSDoc comments on docs websites with domain-specific behavior like backticked identifiers linkifying to API docs
- dynamic content from untrusted users, with granular control over enabled behaviors
mdz's predictable grammar means streaming never re-parses: LLM output renders incrementally,
and the infrequent corrections -- like an unclosed ** or a link resolving late --
are bounded, explicit opcodes. The streaming design is derived from @pngwn's ideas in this Bluesky thread (pngwn.at), and other parts of the mdz design are inspired by
pngwn's MDsveX.
mdz takes after CommonMark and GFM, diverging where their rules fight streaming or carry avoidable complexity and ambiguity.
It ships:
- Mdz and MdzStream: Svelte 5 components to render regular and streaming content
- MdzStreamParser: an incremental parser that emits append-only rendering opcodes as chunks arrive, never re-parsing
- mdz_parse: a synchronous parser producing an MdzNode tree
- svelte_preprocess_mdz: a preprocessor that compiles static mdz content to plain Svelte markup at build time
AI disclosure: almost everything here was generated by machine agents except for the designs and intros. mdz is the result of 5+ years of prototyping ideas.
For more see the docs ahead and the repo.
Principles #
- Streaming is a property of the grammar, not just the parser — every ambiguity resolves within a bounded hold or an explicit, local opcode; constructs that break this (setext headings, reference links, the loose/tight list distinction) are excluded rather than worked around.
- One syntax per feature —
**bold**,_italic_,~~strike~~, no alternate spellings. - Chunking doesn't change the result — the streamed tree matches the one-shot parse (documented adversarial cases aside), and the parser never re-parses.
- Predictable over permissive — false negatives over false positives:
__init__, intraword underscores, and stray*stay literal. - Structure is the parser's job; presentation is the renderer's — whitespace style, syntax highlighting, and rich components inject at the rendering seam.
- Nothing renders by default except mdz — HTML elements and Svelte components must be registered; unregistered tags render as visible placeholders.
Install #
npm i -D @fuzdev/mdz Svelte, SvelteKit, and @fuzdev/fuz_util are peer dependencies (fuz_util is used only
by the build-time preprocessor).
Usage #
Render static content:
import Mdz from '@fuzdev/mdz/Mdz.svelte'; <Mdz content="Some **bold** and `code` and a [link](/docs)." /> Parse to a node tree:
import {mdz_parse} from '@fuzdev/mdz/mdz.js';
const nodes = mdz_parse('# Heading\n\nSome **bold** text.'); The usage docs walk through the full dialect with interactive examples, and the formal grammar is the normative syntax reference.
Streaming #
Feed chunks to MdzStreamParser as they arrive (e.g. from an LLM) and render the emitted opcodes with MdzStream — the final tree is identical to what mdz_parse produces for the same input, regardless of chunking. See the streaming docs for the live demo, usage, and the opcode design.
Rendering seam #
By default, inline `code` renders as plain <code> and fenced
code blocks render as plain <pre><code>. Inject richer renderers via MdzRoot — the prop contracts match fuz_ui's DocsLink (auto-linked API identifiers) and fuz_code's Code (syntax highlighting):
<MdzRoot code={DocsLink} codeblock={Code}>
<Mdz content={content} />
</MdzRoot>Preprocessor #
svelte_preprocess_mdz compiles static <Mdz content="…"> usages to pre-rendered markup at build time, eliminating
runtime parsing for known-static content — see the svelte_preprocess_mdz docs
for setup and options.