Markdown¶
Renders a GitHub-Flavored-Markdown subset by composing existing widgets
(text, richText, box, row, column, divider). Non-interactive,
experimental tier.
Usage¶
ui.markdown(prBody)
ui.markdown("# Title\n\nShips `ui.markdown` with **GFM subset** support.", {
blockGap: 1,
})
Props¶
| Prop | Type | Default | Description |
|---|---|---|---|
source |
string |
required (first argument) | Markdown source text |
blockGap |
number |
1 |
Vertical spacing rows between top-level blocks |
key |
string |
- | Reconciliation key |
Supported syntax¶
| Construct | Notes |
|---|---|
ATX headings #–###### |
Level styles are attribute-only (bold/underline/dim) |
| Paragraphs | Soft breaks join with spaces; or \ at line end hard-breaks |
**strong**, *em*, ~~del~~ |
_underscore_ emphasis respects word boundaries |
`code` |
Rendered inverse; double-backtick spans contain backticks |
[label](url) / <https://…> / bare URLs |
Rendered underlined; trailing punctuation trimmed from bare URLs |
| Fenced + indented code blocks | Fence info maps to CodeEditor tokenizer presets for monochrome syntax emphasis |
| Lists | Ordered (1. / 1), start number kept), unordered, nested, tight |
Task items - [x] |
Checked markers render dim |
| Blockquotes | GitHub-style dim left bar (nesting supported) |
| Pipe tables | :-, :-:, -: alignment; \| escapes; ragged rows normalized |
--- / * * * / ___ |
Thematic break renders as a divider |
| Entities | &, <, >, ", ', , numeric forms |
Intentional divergences from full GFM: no setext headings, reference links, images, footnotes, or raw HTML (HTML tags render as literal text), no lazy paragraph continuation, and the CommonMark emphasis algorithm is approximated with flanking checks.
Notes¶
- Parsing never throws. Malformed constructs degrade to literal text, nesting depth is bounded, and inline scanning runs on a work budget so adversarial input (PR bodies, agent output) cannot trigger quadratic blowups.
- Styling is attribute-only, so output adapts to every theme; no colors are hard-coded.
- Styled paragraphs wrap at word boundaries; unstyled paragraphs use
grapheme-safe
textwrapping. - For append-only sources use
createMarkdownStream()(see below); for static documents that re-render often, pre-parse withparseMarkdown(source)and render withrenderMarkdown(doc, options). - The parsed
MarkdownDocumentAST (MarkdownBlock/MarkdownInline) is exported and deeply frozen.
Streaming¶
For append-only sources — agent transcripts, live logs — use
createMarkdownStream(options?). Under append-only input every top-level
block except the last is immutable, so the stream re-parses only the tail
block per append and caches both parsed blocks and their rendered VNodes.
Completed blocks keep referential identity, so reconciliation and layout
stability signatures skip untouched subtrees; appends stay O(tail) instead of
O(document).
import { createMarkdownStream } from "@rezi-ui/core";
const stream = createMarkdownStream({ blockGap: 1 });
onTokens((chunk) => {
stream.append(chunk); // chunks may split lines, CRLF pairs, or words
app.update((s) => ({ ...s, transcriptRev: s.transcriptRev + 1 }));
});
app.view((state) => ui.box({ border: "none" }, [stream.vnode()]));
The stream guarantees that stream.document() always deep-equals
parseMarkdown(stream.source()) regardless of chunk boundaries.
reset(source?) clears the buffer; document() exposes the parsed AST.