Docs Platform

Markdown

Markdown is a data pipeline, not a string-to-HTML shortcut. The core parser produces a stable AST and diagnostics; the render plan turns that AST into framework-neutral parts; each adapter decides how those parts become DOM.

Live pipeline

The shipped parser and render plan, re-run on every edit. The output uses the same styled components the rest of the kit ships — Blockquote, List, task items, code blocks.

Source · edit me

Rendered · the shipped pipeline

Release 0.9

Grassroot's markdown is a data pipeline: parse to an AST, plan the render, then map plan nodes to components.

Blockquotes, task lists, and tables all resolve to the same styled components the rest of the kit ships.

ts
const plan = renderPlan(parse(source));

Explore the layers

The headless contract lives in @grassroot/ui-headless-core/markdown and is re-exported by @grassroot/ui-headless-{react,vue,solid,svelte,angular}/markdown. The styled roots live in the corresponding @grassroot/ui-* packages. Markdown is intentionally stateless: it has no machine registry item and does not pull in @grassroot/ui-machines.

Surface
Entry point
Responsibility
Core
parse, toRenderPlan
AST, diagnostics, safe links, positions, and render parts.
Headless
Markdown
SSR-safe framework markup with no visual recipe.
Styled
Markdown
Maps headings, code, callouts, tasks, details, lists, figures, and footnotes to Grassroot components.
Vanilla
mountMarkdown
Framework-free DOM mount using text nodes for untrusted content.
Surface
Core
Entry point
parse, toRenderPlan
Responsibility
AST, diagnostics, safe links, positions, and render parts.
Surface
Headless
Entry point
Markdown
Responsibility
SSR-safe framework markup with no visual recipe.
Surface
Styled
Entry point
Markdown
Responsibility
Maps headings, code, callouts, tasks, details, lists, figures, and footnotes to Grassroot components.
Surface
Vanilla
Entry point
mountMarkdown
Responsibility
Framework-free DOM mount using text nodes for untrusted content.

Parse to render plan

parse(source, options) handles headings, paragraphs, fenced and indented code, lists and tasks, blockquotes and callouts, tables, details, reference links, footnotes, inline emphasis, code spans, autolinks, images, and hard breaks. Every block has a deterministic id; source positions and diagnostics are optional outputs controlled by the parse options.

const document = parse(source, {
  gfm: true,
  footnotes: true,
  callouts: true,
  sourcePositions: true,
});

const plan = toRenderPlan(document, {
  code: { ts: (value, language) => highlight(value, language) },
});

Renderer overrides are keyed by semantic node type, while code highlighters are keyed by language and win over the default tokenizer. Render tokens are grouped per source line so adapters can preserve whitespace and line structure during SSR.

Security

Raw HTML is represented as html-blocked and rendered as escaped text. Link and image destinations pass through sanitizeUrl; unsafe schemes such as javascript:, vbscript:, and unsafe data URLs are dropped with a diagnostic. The vanilla and framework adapters use text nodes or framework interpolation, never an HTML sink for Markdown source.

Streaming and editing

parseIncremental returns the next document plus an incremental state and preserves ids for unchanged blocks. The editor bridge converts the AST to a small ProseMirror/Tiptap-shaped node model and serializes edited nodes back through the same parser. Canonical serialization normalizes headings, lists, fences, and spacing so a parse/serialize cycle is deterministic.

Editor round-trip

The editor bridge is deliberately structural. It exposes plain JSON nodes and marks so the Rich Text Editor tranche can consume Markdown without a package dependency on the editor machine. Formatting is canonical rather than source-preserving: edited nodes serialize through serializeMarkdown, then return to the AST through parse.

const nodes = parseToEditorNodes(source);
const edited = editorNodesToAst(nodes);
const markdown = serializeMarkdown(edited);
const document = parse(markdown);

M-4's EditorBridgeNode contract is frozen as of this close-out; the Editor plan's E-4 slice should re-review it against its actual model/schema.ts before consuming it, per the plan's own risk table.

Framework adapters

React, Vue, Solid, Svelte, and Angular expose the same source/AST/options contract. Their headless roots are SSR-safe and avoid effects for static rendering; the styled roots add the framework's existing Heading, CodeBlock, Alert, Checkbox, and Collapsible primitives where available. The browser contract exercises the same scenarios across all five adapters and the vanilla mount.