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`.

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:// or http://
  • 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
  1. starts at three
  2. authored numbers are preserved
  3. 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>
use it or lose it

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>

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:

FeatureCommonMark/GFMmdz
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 headingstext + --- = h2none — --- is always an HR
indented code4-space indentnone — fenced only
lazy continuationyes — dedented/unprefixed lines continue lists and quotesnone — list continuation must be indented past the marker; every quote line carries its prefix
reference links[text][ref]none — inline [text](url) only
hard breakstrailing double-space or \<br /> (registered element)
relative path linksnot 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