Lynx/Modules/Markdown/MarkdownView
@sigx/lynx-markdown · Stable · Component library

MarkdownView#

SignalX-native, streaming-aware markdown renderer. Parses markdown in JS and renders to Lynx primitives — identical on iOS, Android, and Harmony, with no native module.

Import#

TSX
import { MarkdownView } from '@sigx/lynx-markdown';

MarkdownView lives on the package root, which pulls in no runtime peers — renderer-only consumers pay nothing and have nothing to prebuild.

Basic usage#

value is the markdown source and the only prop you usually need. It is reactive: pass a signal's value and the view re-renders as the source grows.

TSX
import { MarkdownView } from '@sigx/lynx-markdown';

function Doc() {
  return (
    <MarkdownView value={'# Hello\n\nThis is **markdown** rendered to native views.'} />
  );
}

Supports CommonMark + GFM: headings, bold/italic/strike, inline code, links, images, autolinks, nested ordered/unordered lists, GFM task lists, blockquotes, fenced code, thematic breaks, and tables with per-column alignment. Unsupported constructs (raw HTML, reference-style links, setext headings, syntax highlighting) render as literal text rather than throwing.

The default renderers never navigate on their own — wire onLink and onImageTap to act on taps. Hrefs are sanitized first: only http(s):, mailto:, tel:, and scheme-less links reach onLink; javascript:/data: collapse to #.

TSX
import { MarkdownView } from '@sigx/lynx-markdown';

function Article(props: { source: string }) {
  return (
    <MarkdownView
      value={props.source}
      onLink={(href) => openInBrowser(href)}
      onImageTap={(src) => showLightbox(src)}
    />
  );
}

The default image renderer shows the alt text (or src) as a tappable link, not a real <image> — supply your own image component via components for inline pictures.

Streaming AI output#

Pair MarkdownView with createMarkdownStream to render a token loop flicker-free: finalized blocks keep stable keys and are never re-parsed or remounted, so completed paragraphs and code blocks never reflow.

TSX
import { MarkdownView, createMarkdownStream } from '@sigx/lynx-markdown';

const stream = createMarkdownStream({ flushIntervalMs: 16 });

async function generate(prompt: string) {
  stream.reset();
  for await (const token of askModel(prompt)) stream.append(token);
  stream.done();
}

function ChatBubble() {
  return <MarkdownView value={stream.value.value} />;
}

Theming with a component map#

MarkdownView is design-system agnostic. It ships neutral defaultComponents and merges your partial components map over them — any node type you omit falls back to the default. Block renderers return a JSXElement; inline renderers return a MarkdownChild (a JSX element or a string).

TSX
import { MarkdownView } from '@sigx/lynx-markdown';

const components = {
  heading: (p) => (
    <text style={{ fontSize: p.level === 1 ? 28 : 20, fontWeight: 'bold' }}>
      {p.children}
    </text>
  ),
  strong: (p) => <text style={{ fontWeight: 'bold', color: '#7c3aed' }}>{p.children}</text>,
};

function Themed(props: { source: string }) {
  return <MarkdownView value={props.source} components={components} />;
}

For a ready-made theme, @sigx/lynx-daisyui exports markdownComponents — a complete daisyUI-styled map you can pass straight to components.

Mention previews via a parser extension#

MarkdownView is not plugin-aware, but it accepts raw parser extensions plus an extension component map, so you can render plugin syntax in read-only output. To show @[label](id) chips, pass the standalone mentionSyntax extension and map its node to the plugin's preview component.

TSX
import { MarkdownView, createMentionPlugin, mentionSyntax } from '@sigx/lynx-markdown';

const plugin = createMentionPlugin({ search: (q) => findPeople(q) });
const components = { extension: { mention: plugin.inline.component } };
const extensions = [mentionSyntax] as const;

function Transcript(props: { source: string }) {
  return <MarkdownView value={props.source} extensions={extensions} components={components} />;
}

Keep extensions a stable array reference (a module constant, not an inline literal). Changing its identity recreates the parse engine and re-parses from scratch, defeating the incremental streaming optimization.

Props#

All props are optional.

PropTypeDescription
valuestringThe markdown source. Reactive and parsed incrementally.
onLink(href: string) => voidCalled with a sanitized href when a link is tapped.
onImageTap(src: string) => voidCalled with the image src when an image is tapped.
componentsPartial<MarkdownComponents>Per-node render overrides, merged over defaultComponents.
extensionsreadonly ParserInlineExtension[]Stable array of inline parser extensions (e.g. mentionSyntax).

See the API reference for the full MarkdownComponents map and the parser/AST types.