usage #
This section has each feature's syntax with live rendered examples. The formal grammar is the normative syntax reference, and the introduction covers what mdz is for and the principles behind it.
mdz was created to author content with Svelte components
and to render TSDoc/JSDoc comments on docs websites — hence the domain-specific behavior:
linkified `backtick-wrapped` declarations and modules, auto-detected URLs prefixed with https://, /, ./, and ../, and registered
Svelte components in content.
import Mdz from '@fuzdev/mdz/Mdz.svelte'; <Mdz content="Some **bold** and `code` and a [link](/docs)." /> Some bold and code and a link.
Playground #
Bold and italic and strikethrough text.
Inline links to identifiers using backticks: mdz_parse, Mdz
A heading
A paragraph with links: fuz homepage, ./grammar
const y = 1336;Streaming #
mdz renders content incrementally as it arrives (e.g. from an LLM), with no re-parsing. See the streaming docs for the live demo, the opcode design, and the three rendering paths.
Basic formatting #
Supports bold, italic, and strikethrough:
<Mdz content="**Bold** and _italic_ and ~~strikethrough~~ text." /> Bold and italic and strikethrough text.
All inline formatting can nest:
<Mdz content="**~~_All_ three~~ combi**_ned_" /> All three combined
Inline code auto-linking #
Backtick code automatically links to identifiers and modules:
To parse markdown directly, use `mdz_parse` from module `mdz.ts`. Non-identifiers become plain code elements:
This `identifier` does not exist. This identifier does not exist.
Links #
mdz supports four kinds of links:
- standard markdown link syntax
- external URLs starting with
https://orhttp:// - absolute paths starting with
/ - relative paths starting with
./or../
[Fuz API docs](https://fuz.dev/docs/api) and https://fuz.dev/docs/api and /docs/api Relative paths are resolved against the base context (set via MdzRoot) when provided, producing correct absolute paths. Without base, they use raw hrefs (the browser resolves them against the current URL):
See ./grammar and ../streaming and ../usage for relative paths. See ./grammar and ../streaming and ../usage for relative paths.
Line breaks and paragraphs #
Single newlines are soft breaks, like standard markdown — they render as spaces by default:
First line.
Second line.
Third line. First line. Second line. Third line.
Double newlines create paragraph breaks:
First paragraph.
Second paragraph.
Soft break in second paragraph. First paragraph.
Second paragraph. Soft break in second paragraph.
Three or more newlines are the same as two — one paragraph break:
First paragraph.
Second paragraph separated by an extra newline. First paragraph.
Second paragraph separated by an extra newline.
To force a line break within a paragraph, use an explicit <br /> (it's an
HTML element, so it must be registered):
First line.<br />Second line. First line.
Second line.
To instead render every newline as a line break — handy for chat-style user input — see the whitespace prop in the whitespace section.
Headings #
Use 1-6 hashes followed by a space:
#### h4 ~~with~~ _italic_ h4 with italic
Must start at column 0 and have a space after hashes. No blank lines are required around headings. Headings can include inline formatting.
Lists #
Unordered items use -, ordered items use 1. — each followed by a space.
A marker at column 0 starts a list; indenting nests:
- first item
- second **item**
- nested item
- third item - first item
- second item
- nested item
- third item
Ordered lists render GFM-style: the first item's number sets start and the
browser numbers the rest, so the 1./1./1. reordering idiom
works — but authored numbers are preserved in the AST:
3. starts at three
1. authored numbers are preserved
2. but render in order - starts at three
- authored numbers are preserved
- but render in order
Blank lines between items don't end the list (loose LLM-style lists keep their structure), items render tight, and a column-0 non-marker line always ends the list — there is no lazy continuation. Items can contain paragraphs, nested lists, code blocks, and blockquotes on their own indented lines. See the formal grammar for the precise rules.
Blockquotes #
Prefix each line with > and a space — a quote's content is a mini-document,
so headings, lists, code blocks, and deeper quotes all work inside. A bare > line breaks paragraphs within the quote, and a blank line ends it:
> A quote with **formatting** and `code`.
> Same paragraph, soft break.
>
> New paragraph via bare `>`.
>> Nested quote. A quote with formatting and
code. Same paragraph, soft break.New paragraph via bare
>.Nested quote.
The space is required (>a is literal text) and there is no lazy continuation —
every quoted line carries the prefix.
Code blocks #
mdz uses fuz_code for syntax highlighting. Use three or more backticks with optional language hint:
```ts
const z: number = 43;
``` const z: number = 43;Must start at column 0; the closing fence needs at least as many backticks as the opening fence. Empty code blocks are valid. No blank lines are required around code blocks.
Horizontal rules #
Use exactly three hyphens (---) at the start of a line to create a horizontal
rule. No blank lines are required around it. mdz has no setext headings, so --- after a paragraph is always an HR:
Section one.
---
Section two. Section one.
Section two.
Whitespace #
The parser preserves whitespace in text nodes exactly as authored — single newlines stay
literal \n characters and no <br> nodes are created. How
that whitespace renders is the consumer's choice via the whitespace prop, which sets white-space on the wrapper element. By default no style is applied,
so whitespace collapses like standard markdown:
<Mdz content=" see
how
whitespace
renders " /> see how whitespace renders
With whitespace="pre-line", every newline renders as a line break while spaces
still collapse — useful for chat-style user input where pressing Enter should mean a new
line:
<Mdz content=" see
how
whitespace
renders " whitespace="pre-line" /> see how whitespace renders
With whitespace="pre-wrap", spaces, tabs, and newlines are all rendered
faithfully:
<Mdz content=" see
how
whitespace
renders " whitespace="pre-wrap" /> see how whitespace renders
HTML elements #
mdz supports an opt-in set of HTML elements for semantic markup and styling.
<aside>This is _italicized <code>code</code>_ inside an `aside`.</aside> <marquee>use it or lose it</marquee> Elements must be registered:
<MdzRoot elements={new Map([['code', true], ['aside', true], ['marquee', true], ['br', true]])}>
<Mdz content="<aside>text</aside>" />
</MdzRoot> Unregistered elements render as <tag-name /> placeholders for security.
Svelte components #
mdz supports a minimal subset of Svelte component syntax — tags with children, no props yet. Components are distinguished from HTML elements by their uppercase first letter:
<Alert>This is an `Alert` with _italicized <code>code</code>_ inside.</Alert> Alert with italicized code inside.Components must be registered:
<MdzRoot components={new Map([['Alert', Alert]])}>
<Mdz content="<Alert>warning</Alert>" />
</MdzRoot> Unregistered components render as <ComponentName /> placeholders.
Advanced usage #
For more control, use mdz_parse directly with MdzNodeView:
import {mdz_parse} from '@fuzdev/mdz/mdz.js';
import MdzNodeView from '@fuzdev/mdz/MdzNodeView.svelte';
const nodes = mdz_parse(content); <div class="custom">
{#each nodes as node}
<MdzNodeView {node} />
{/each}
</div> You own the container, so you control presentation — for example apply white-space:pre-line to render newlines as line breaks, or white-space:pre to avoid wrapping.
Compatibility with other markdowns #
mdz takes after CommonMark and GFM — their behavior is the baseline wherever it fits streaming. But mdz is a dialect, not a subset: it deliberately supports one syntax per feature and drops constructs whose ambiguity costs more than they're worth. The highlights:
| Feature | CommonMark/GFM | mdz |
|---|---|---|
| bold | **text** or __text__ | **text** only |
| italic | *text* or _text_ | _text_ only — single * is literal |
| strikethrough | ~~text~~, and ~text~ on github.com | ~~text~~ only — a single ~ is always literal, so ~/dev/paths never strike |
| doubled delimiters | __text__ bold, ~~text~~ strike | ~~text~~ strikes; __text__ stays literal — underscore runs
never pair, so __init__ is safe |
| setext headings | text + --- = h2 | none — --- is always an HR |
| indented code | 4-space indent | none — fenced only |
| lazy continuation | yes — dedented/unprefixed lines continue lists and quotes | none — list continuation must be indented past the marker; every quote line carries its prefix |
| reference links | [text][ref] | none — inline [text](url) only |
| hard breaks | trailing double-space or \ | <br /> (registered element) |
| relative path links | not auto-linked | ./path and ../path auto-link |
Block elements (headings, HR, codeblocks, lists, blockquotes) can interrupt paragraphs without blank lines, while inline formatting prefers false negatives over false positives. Every smaller divergence — list nesting and empty items, blockquote prefixes, tag scoping, link parsing — is enumerated in the full divergence table on the formal grammar page.
More docs #
- streaming — live demo, rendering paths, opcode design, and the streaming model
- svelte_preprocess_mdz — compiles static content to plain Svelte markup at build time
- grammar — the formal grammar (the normative syntax reference) and the full CommonMark/GFM divergence table
- fixtures — renders every test fixture live with its parsed JSON