gpt_markdown logo

gpt_markdown

The Flutter renderer for AI output.

Production-grade Markdown and LaTeX rendering for streaming Flutter AI interfaces.
Render rich assistant replies, math, code, tables, citations, images, and custom inline UI in one widget.

Pub Version Pub Likes Pub Points BSD-3-Clause license

๐ŸŒ Website ยท ๐Ÿ“– Documentation ยท ๐ŸŽฎ Live Playground ยท ๐Ÿ“ฆ pub.dev


โœจ Why gpt_markdown?

  • Built for AI output โ€” Markdown, LaTeX, code blocks, tables, citations, images, task lists, and mixed rich content in one response.
  • Streaming that stays fast โ€” settled segments are reused while the live tail updates. At 12 KB, each update is 31ร— faster than in 1.2.1โ€”or 74ร— faster with the lazy sliver. See the benchmarks.
  • Production-level control โ€” style sheets, Flutter theme extensions, component builders, callbacks, and custom components.
  • Extensible inline UI โ€” add @mentions, #channels, :emoji:, issue references, and product-specific syntax without forking the renderer.
  • Designed for real-world edge cases โ€” RTL, text scaling, selection, malformed Markdown, autolinks, nested content, reduced motion, Flutter web, and WASM.

๐Ÿงฉ Everything AI output needs

Rendering Production experience Extensibility
๐Ÿ“ Rich Markdown โšก Adaptive streaming ๐ŸŽจ Component style sheet
โˆ‘ Inline and block LaTeX ๐Ÿš€ Lazy sliver rendering ๐Ÿงฑ Structural builders
๐Ÿ’ป Syntax-highlighted code โ™ฟ Selection and text scaling ๐Ÿท๏ธ Mentions, channels, and emoji
๐Ÿ“Š Tables and aligned columns ๐ŸŒ RTL, web, and WASM ๐Ÿงฉ Custom components and scopes
๐Ÿ”— Links, autolinks, and images ๐ŸŒ“ Theme-aware rendering ๐Ÿ‘† Interaction callbacks
โ˜‘๏ธ Lists, tasks, and citations ๐Ÿ›ก๏ธ Graceful malformed input ๐Ÿ“ฑ Custom URL schemes

๐Ÿ–ผ๏ธ What it renders

Every image is one GptMarkdown widget with no styling applied โ€” the defaults, in a dark theme. Click any of them for full size.

Rich text rendered by gpt_markdown
Rich text
Headings, emphasis, lists, quotes, rules, autolinks.
LaTeX rendered by gpt_markdown
LaTeX
Inline and display equations, on the text baseline.
Tables rendered by gpt_markdown
Tables
Per-column alignment, Markdown inside cells.
Code rendered by gpt_markdown
Code
Syntax highlighting, language labels, and copy controls.
Task lists rendered by gpt_markdown
Task lists
Checkboxes, ordered and nested lists, citation tags.
Inline patterns rendered by gpt_markdown
Inline patterns
Mentions, channels, shortcodes. #2959 stays text.

๐Ÿ› ๏ธ Quick start

flutter pub add gpt_markdown
import 'package:gpt_markdown/gpt_markdown.dart';

GptMarkdown(
  reply,
  onLinkTap: (url, title) => openUrl(url),
)

The widget sizes itself to its content. Place it inside your preferred scrollable chat or document surface.

โšก Streaming AI responses

Rebuild GptMarkdown with the complete text received so far. Settled segments are reused while the changing tail updates.

GptMarkdown(
  streamedReply,
  animation: GptMarkdownAnimation.fade,
  blockAnimation: GptMarkdownBlockAnimation.fadeIn,
  isStreaming: stillGenerating,
  charactersPerSecond: 300,
)

The reveal adapts to incoming text, finishes when generation ends, and respects reduced-motion settings. See the streaming guide for animation modes and configuration.

For long responses and documents, use SliverGptMarkdown inside a CustomScrollView. It creates spans and widgets only for the segments requested by the viewport, including its cache extent. The regular GptMarkdown widget is cheaper for short content and remains the option for character reveal. See the rendering architecture guide.

๐Ÿ“ Markdown, LaTeX, and rich AI output

GptMarkdown(
  r'''
## Revenue forecast

Projected growth: **18%**.

\[
R_{next} = R_{current} \times (1 + 0.18)
\]

- [x] Validate the assumptions
- [ ] Review the final forecast

Sources: [1] [2]
  ''',
  onSourceTagTap: (source) => openSource(source),
)

Use \( ... \) for inline LaTeX and \[ ... \] for block equations. Enable dollar-sign syntax with useDollarSignsForLatex: true. Code fences include syntax highlighting, language labels, and copy controls.

Wrap the renderer with SelectionArea when selectable output is needed:

SelectionArea(
  child: GptMarkdown(reply),
)

๐ŸŽจ Make it match your product

Use style objects for appearance and builders when you need to replace structure.

GptMarkdown(
  reply,
  styleSheet: const GptMarkdownStyleSheet(
    blockQuote: BlockQuoteStyle(
      barWidth: 4,
      barColor: Colors.indigo,
    ),
    inlineCode: InlineCodeStyle(
      fontFamily: 'GeistMono',
      borderRadius: Radius.circular(6),
    ),
    codeBlock: CodeBlockStyle(
      borderRadius: Radius.circular(12),
      showCopyButton: true,
    ),
    table: TableStyle(
      cellPadding: EdgeInsets.all(10),
    ),
  ),
  onCodeCopy: (code) => trackCopy(code),
  onImageTap: (url) => openImage(url),
)

Set styles app-wide with GptMarkdownThemeData. Use builders such as codeBuilder, tableBuilder, and imageBuilder to replace components, or span-based builders for links, citations, and inline code. See customization.

๐Ÿท๏ธ App-specific inline UI

Render mentions, channels, emoji, issue references, and other product syntax alongside Markdown:

GptMarkdown(
  reply,
  inlinePatterns: [
    InlinePattern.prefixed(
      prefix: '#',
      knownNames: channelNames,
      builder: (context, match, style) => WidgetSpan(
        alignment: PlaceholderAlignment.baseline,
        baseline: TextBaseline.alphabetic,
        child: ChannelChip(
          name: match.group(0)!.substring(1),
        ),
      ),
    ),
  ],
)

Only known channel names are matched, longest-first. Patterns exclude link labels by default; unrecognized tokens such as #2959 remain text.

Use blockComponents for custom blocks and inlineDirectives for payloads the parser must leave untouched. See custom components.

Upgrading? Passing components or inlineComponents, even an empty list, selects the deprecated legacy parser. Replace them to use the new pipeline. See the migration guide and changelog.

Bare URLs, www. hosts, emails, and angle autolinks work automatically. Add custom schemes with autolinkSchemes: const {'myapp'}, or disable autolinking with autolink: false. Explicit [label](url) links still work. See inline syntax.

๐Ÿ“š Documentation

Guide Covers
Getting started Installation, syntax, taps, LaTeX, RTL, and selection
Customization Style classes, themes, builders, and callbacks
Streaming Pacing, performance, accessibility, and limitations
Rendering architecture Extension registration, lazy rendering, and performance policies
Inline syntax Autolinks, mentions, channels, and scopes
Custom components Block and inline extensions
GptMarkdown options Every constructor option and default
Benchmarks Methodology, results, and limitations
Migration What each release changes, newest first

Built by Val

gpt_markdown is the open-source rendering foundation of Val, the live visual layer for AI agents.

Building an AI product that needs richer output than a text box? Request early access to Val.

๐Ÿ’ฌ Community

Issues and pull requests are welcome on GitHub. If the package helps your project, consider giving it a like on pub.dev or a star on GitHub.

๐Ÿ“„ License

BSD 3-Clause โ€” see LICENSE.

Libraries

custom_widgets/bidi_rich_text
Workaround for https://github.com/flutter/flutter/issues/54400.
custom_widgets/code_field
custom_widgets/custom_divider
custom_widgets/custom_error_image
custom_widgets/custom_rb_cb
custom_widgets/indent_widget
custom_widgets/inline_code
Inline code decoration.
custom_widgets/inline_tap
Tap targets inside a paragraph, resolved by text range rather than by span.
custom_widgets/markdown_config
custom_widgets/markdown_text_scaling
custom_widgets/selectable_adapter
custom_widgets/unordered_ordered_list
gpt_markdown
plusparse/ast
The Markdown document AST produced by the plusparse parser.
plusparse/block_parser
Block parser: walks the document line-by-line and produces block-level MdNodes, recursing into the inline parser for text content and into itself for nested content (list items, blockquotes). Single pass, error-tolerant: it never throws on malformed or partial (streaming) input. Ported 1:1 from the Rust plusparse block parser.
plusparse/block_syntax
Pure-Dart extension points for block syntax. No Flutter dependency.
plusparse/inline_parser
Inline parser: turns a run of text into inline MdNodes (bold, italic, code, links, images, LaTeX, source tags, โ€ฆ). Single forward pass over the characters; emphasis content is parsed recursively. Ported 1:1 from the Rust plusparse inline parser, using code-unit scanning and indexOf instead of a char vector (all delimiters are ASCII, so this is safe and fast on Dart's UTF-16 strings).
plusparse/plusparse
plusparse โ€” a fast, hand-written Markdown + LaTeX parser, fully rewritten in pure Dart and built into gpt_markdown (originally a Rust flutter_rust_bridge plugin).
plusparse/scanner
Low-level, single-pass line helpers shared by the block parser. No regex โ€” everything is hand-written character scanning (ported 1:1 from the Rust plusparse scanner).
plusparse/stream_splitter
Splits Markdown source into independently-parsable top-level segments, used by gpt_markdown's incremental rendering mode.
streaming/block_entrance
The one-shot entrance a block plays the first time it is built.
streaming/inline_hold
Holding the reveal behind markup that has not finished arriving.
streaming/reveal_effect
How streamed content arrives: the character reveal at the head, and the entrance a finished block plays.
streaming/reveal_engine
The arithmetic behind the streaming reveal.
streaming/reveal_spans
Applying a character reveal to spans that are already built.
streaming/stream_split
Finding a safe place to cut streaming Markdown in two.
streaming/streaming_markdown
Reveal animation for Markdown that arrives a token at a time.
styles/block_quote_style
styles/checkbox_style
styles/code_block_style
styles/gpt_markdown_style_sheet
styles/heading_style
styles/hr_style
styles/image_style
styles/latex_style
styles/list_style
styles/source_tag_style
styles/style_lerp
Interpolation helpers shared by the style classes.
styles/table_style