Skip to main content

Quick Start

Create and start one parser for each active Markdown stream:

final parser = MarkdownStreamParser();
await parser.start();

Use replace when you receive a full document snapshot:

final result = await parser.replace('# Hello');

Use append when you receive only the next chunk:

final next = await parser.append('\n\nStreaming **markdown** chunk...');

Render the latest parsed blocks:

AnimatedStreamingMarkdown(
blocks: next.blocks,
tokenStaggerDelay: const Duration(milliseconds: 120),
tokenAnimationDuration: const Duration(milliseconds: 220),
enableSelection: true,
);

The renderer's default token effect is a Fade reveal. Set tokenAnimationBuilder only when your product wants another motion style.

Dispose the parser when the stream is finished:

parser.dispose();

Render a Complete Markdown String

For short, already-complete Markdown, use the synchronous factory to parse and paint without starting an isolate-backed stream parser:

AnimatedStreamingMarkdown.fromMarkdown(
markdown: '# Hello\n\nRendered from a complete Markdown string.',
tokenStaggerDelay: Duration.zero,
tokenAnimationDuration: Duration.zero,
);

Use MarkdownStreamParser for long-running streams. Use AnimatedStreamingMarkdown.fromMarkdown when you already have a full Markdown snapshot and want the shortest setup path.

Warm Up Parser Resources

Warm parser resources before the first visible render when startup latency is important:

await warmUpStreamingMarkdownParser(includeWorker: true);

Sliver Layouts

Set asSliver when the renderer is placed inside a CustomScrollView:

final selectionController = AnimatedMarkdownSelectionController();

AnimatedStreamingMarkdownSelectionArea(
controller: selectionController,
child: CustomScrollView(
slivers: [
AnimatedStreamingMarkdown(
blocks: result.blocks,
asSliver: true,
enableSelection: true,
selectionController: selectionController,
),
],
),
);

The wrapper is required only when sliver selection is enabled. It preserves lazy rendering and manages exactly one Markdown renderer. Dispose an app-owned controller with the surrounding State.

Selection is source-backed and directional. Dragging or moving a handle near an edge advances scrolling frame by frame like a TextField; releasing or cancelling stops it immediately and does not add fling momentum. Images, LaTeX, tables, code, lists, and HTML blocks participate in the same selection range, and the highlight is a single flat layer per visual line.

Custom Token Animation

Use tokenAnimationBuilder to customize each token reveal:

AnimatedStreamingMarkdown(
blocks: result.blocks,
tokenStaggerDelay: const Duration(milliseconds: 80),
tokenAnimationDuration: const Duration(milliseconds: 240),
tokenAnimationBuilder: (context, token) {
final t = Curves.easeOutCubic.transform(token.value);
return Transform.translate(
offset: Offset(0, (1 - t) * 8),
child: Opacity(opacity: t, child: token.child),
);
},
);

Selection During Scroll

When enableSelection is active, selection is stored as stable Markdown source offsets. Dragging near a vertical viewport edge auto-scrolls the surrounding scrollable; dragging inside an overflowed table near its left or right edge auto-scrolls that table so the range can keep growing. Releasing the pointer or handle stops scrolling without momentum. Keyboard and controller changes reveal the changed endpoint with 20 logical pixels of padding by default. Keep the parser and renderer state alive across streamed appends; use stable keys when renderers are children of a virtualized message list.