7.5k

Markdown

Renders a markdown string (a GFM subset, including pipe tables and safe presentational HTML) as native widgets through the same text pipeline as every other component — deterministic layout, selectable text. source is required and must be one {binding}; the element takes no children. Applications can pass already-loaded image mappings through images, links dispatch on-link with the URL as payload (bare URLs autolink), <details> blocks toggle through on-details plus a model-owned details-expanded flag list, and #123 references linkify through issue-link-base. Fenced blocks lower through the reusable Code component, so indentation and syntax behavior stay identical.

markdown
A markdown document rendered by the engine (light theme)
headings, emphasis, inline code, lists, links, and a code block

Markup

<markdown source="{release_notes}" images="{markdown_images}" on-link="open_link" issue-link-base="https://github.com/native-sdk/native/issues/"></markdown>

Programmatic construction (Zig)

The builder is the canvas.markdown module, parameterized over the app's Msg type; on_link pairs with Ui.linkMsg(.tag).

const Md = native_sdk.canvas.markdown.Markdown(Msg);

Md.view(ui, model.release_notes, .{
    .images = model.markdown_images,
    .on_link = Ui.linkMsg(.open_link),
    .issue_link_base = "https://github.com/native-sdk/native/issues/",
})

Image discovery writes canonical, entity-decoded URLs into caller-owned bounded storage. Consume each value() while that storage is alive, copy accepted sources into the model, and use the same bytes for both fx.loadImage and the eventual ResolvedImage.source mapping:

var source_storage: [canvas.markdown.max_markdown_images]canvas.markdown.CollectedImageSource = undefined;
const sources = canvas.markdown.collectImageSources(model.release_notes, &source_storage);
for (sources) |*collected| {
    const source = collected.value();
    // Validate the scheme, copy source into the model, then issue fx.loadImage.
}

HTML subset

README- and comment-style HTML is lowered onto native presentation rather than passed to a browser:

  • Text: <b>, <strong>, <i>, <em>, <var>, <s>, <strike>, <del>, <u>, <ins>, <code>, <kbd>, <samp>, <tt>, <mark>, <small>, <sub>, and <sup>.
  • Content: <a href>, <img src alt width height>, <br>, <wbr>, <q>, HTML comments, core named entities, and numeric entities. A leading image in a paragraph, heading, list item, blockquote, or pipe-table cell becomes a native image when images contains a successful canvas.markdown.ResolvedImage mapping for its source; unresolved and mid-paragraph images fall back to alt text. The view never fetches remote media — discover bounded sources with canvas.markdown.collectImageSources, read each caller-owned CollectedImageSource through value(), load it through fx.loadImage, retain successful ids and dimensions in the model, and pass mappings with that canonical source back on the next rebuild.
  • Blocks and wrappers: <h1> through <h6>, <p>, <blockquote>, <pre>, <hr>, list items, and the common <div align="center"> README pattern. Harmless list, table, and container wrappers are accepted as readable native content; use Markdown pipe tables for full native table layout. <details> and <summary> retain their controlled expansion behavior.

This is a sanitized native subset, not a DOM. CSS, scripts, forms, embeds, event attributes, and arbitrary HTML do not execute; unsupported or malformed tags remain literal text. Only href, image src/alt/dimensions, and block align affect presentation.

Attributes

AttributeDescription
sourcemarkdown: one {binding} producing the markdown text (a []const u8 field or fn; arena fns work). Required.
imagesmarkdown: one {binding} producing []const canvas.markdown.ResolvedImage (arena fns work). Each mapping pairs a leading image source with an image id already loaded and registered through fx.loadImage plus its decoded dimensions; views never perform image I/O.
on-linkmarkdown: bare Msg tag dispatched on link press; its payload is the URL ([]const u8 variant).
on-detailsmarkdown: bare Msg tag dispatched on a <details> summary press; its payload is the block index (usize variant).
details-expandedmarkdown: {binding} naming a []const bool iterable of expanded flags, in details-block document order.
issue-link-basemarkdown: literal URL prefix or one {binding}; '#123' refs become links to base ++ number (ghissue:// or https://github.com/owner/repo/issues/).