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.