flutter_ai_chat_markdown 0.1.1
flutter_ai_chat_markdown: ^0.1.1 copied to clipboard
A rich markdown renderer for Flutter AI chat apps. Supports streaming, math (LaTeX), chemistry (mhchem), biology sequences, charts, syntax highlighting, and full theming.
flutter_ai_chat_markdown #
A rich, production-ready Markdown renderer for Flutter AI chat apps — built to match ChatGPT-quality output.
Supports streaming, LaTeX math, chemistry (mhchem), biology sequences, interactive charts, syntax-highlighted code blocks, tables, images with zoom, callout admonitions, and a fully customisable theme system.
Features #
| Category | What's included |
|---|---|
| Text | H1–H6, bold, italic, strikethrough, inline code, links |
| Lists | Ordered, unordered, nested, task lists (- [x]) |
| Code | Syntax highlight (190+ languages), copy button, language label, collapse >50 lines |
| Table | Horizontal scroll, zebra stripe, long-press to copy cell / row / TSV |
| Math | Inline $...$ and block $$...$$ via KaTeX-compatible flutter_math_fork |
| Chemistry | mhchem \ce{} — subscripts, arrows, ion charges, state symbols |
| Biology | Color-coded DNA / RNA / protein sequences, GC% stat |
| Charts | Bar, line, area, pie, scatter, radar from a JSON code fence |
| Images | Remote/local with shimmer placeholder, tap-to-zoom with photo_view |
| Blockquote | Standard quotes + 5 callout types: NOTE, WARNING, TIP, IMPORTANT, CAUTION |
| Streaming | Throttled render (80 ms), stable block keys, blinking caret |
| Theme | Immutable MarkdownTheme with chatGptLight / chatGptDark presets |
| Extensions | Plug in any custom fence-tag renderer without touching core code |
Installation #
dependencies:
flutter_ai_chat_markdown: ^0.1.0
flutter pub get
Quick start #
import 'package:flutter_ai_chat_markdown/flutter_ai_chat_markdown.dart';
// Wrap your app once to provide the theme
MarkdownThemeScope(
theme: MarkdownTheme.chatGptLight,
child: MaterialApp(home: ChatScreen()),
)
// Then render anywhere
MarkdownRenderer(
data: message.content,
streaming: message.isStreaming,
onTapLink: (url) => SafeLink.open(context, url),
)
Streaming #
Pass streaming: true while tokens are arriving. The renderer throttles rebuilds to every 80 ms and only re-renders the incomplete tail block — stable blocks stay constant, so there is no full-widget reflow.
// Start streaming
MarkdownRenderer(data: partialMarkdown, streaming: true)
// When the response is complete
MarkdownRenderer(data: fullMarkdown, streaming: false)
Theme #
Using a preset #
MarkdownThemeScope(
theme: MarkdownTheme.chatGptDark,
child: child,
)
Customising a preset #
final myTheme = MarkdownTheme.chatGptDark.copyWith(
h1: const TextStyle(fontFamily: 'Lora', fontSize: 28, fontWeight: FontWeight.w700),
codeBackground: const Color(0xFF161B22),
linkColor: Colors.tealAccent,
showCaret: true,
caretColor: Colors.tealAccent,
);
Passing a theme directly to a widget #
MarkdownRenderer(
data: markdown,
theme: myTheme, // overrides MarkdownThemeScope
)
Theme tokens #
| Token | Description |
|---|---|
paragraph, h1–h6 |
Typography for body and headings |
code, blockquote, linkStyle |
Inline styles |
tableHeader, tableCell |
Table typography |
blockSpacing |
Vertical gap between blocks |
codePadding, blockquotePadding, tableCellPadding |
Inner spacing |
background, codeBackground, codeBackground |
Surface colours |
blockquoteBar, tableBorder, tableHeaderBg, tableRowAltBg |
Component colours |
linkColor, hrColor, mathColor, chemAtomColor |
Accent colours |
codeRadius, tableRadius, imageRadius |
Corner radii |
codeHighlightTheme |
Highlight.js theme name (e.g. 'atom-one-dark', 'github') |
showCodeLanguageLabel, showCodeCopyButton |
Code block UI toggles |
tokenFadeIn |
Fade duration for new streaming tokens |
showCaret, caretColor |
Blinking caret while streaming |
Supported Markdown syntax #
Inline #
**bold** *italic* ~~strikethrough~~ `inline code`
[link text](https://example.com)
Block elements #
# H1 through ###### H6
- unordered list
1. ordered list
- [x] task list (checked)
- [ ] task list (unchecked)
> regular blockquote
> [!NOTE] informational callout
> [!WARNING] warning callout
> [!TIP] tip callout
> [!IMPORTANT] important callout
> [!CAUTION] caution callout
--- (horizontal rule)
Code blocks (syntax highlighted) #
```python
def greet(name: str) -> str:
return f"Hello, {name}!"
```
Supported: Python, Dart, JavaScript, TypeScript, Go, Rust, Java, Kotlin, Swift, C/C++, C#, SQL, Bash, YAML, JSON, HTML, CSS, and 180+ more via flutter_highlight.
Tables #
| Header A | Header B | Header C |
|:---------|:--------:|---------:|
| left | center | right |
| cell | cell | cell |
Long-press any cell to copy the cell, row, whole table as TSV, or whole table as Markdown.
Math (LaTeX) #
Inline: $a^2 + b^2 = c^2$
Block:
$$
\int_0^\infty e^{-x^2}\,dx = \frac{\sqrt{\pi}}{2}
$$
Supported commands: \frac, \sqrt, \sum, \int, \lim, \begin{pmatrix}, \binom, Greek letters, \mathbb, \mathcal, \cdot, \times, \to, \Rightarrow, and the full KaTeX-compatible subset from flutter_math_fork.
If the LaTeX fails to parse, the raw source is shown with a ⚠ indicator — the app never crashes.
Chemistry (mhchem) #
Use \ce{} inside a math block:
$$\ce{2 H2 + O2 -> 2 H2O}$$
$$\ce{Fe^2+ + 2e- -> Fe}$$
$$\ce{CH3-CH2-OH}$$
The built-in MhchemTransform converts mhchem notation to plain LaTeX before passing it to flutter_math_fork — no WebView required.
Supported: subscripts, superscripts, arrows (->, <->, <=>), state symbols (s) (l) (g) (aq), ion charges.
Biology sequences #
```bio:dna
ATGGCCATTGTAATGGGCCGCTGAAAGGGTGCCCGATAG
```
```bio:rna
AUGGCCAUUGUAAUGGGCCGCUGAAAGGGUGCCCGAUAG
```
```bio:protein
MAIVMGRWKGAR*
```
Each nucleotide / amino acid is colour-coded (A = green, T/U = red, G = yellow, C = blue for nucleic acids; amino acids coloured by physicochemical group). A stats bar shows sequence length and GC% for DNA/RNA.
Charts #
A chart fence takes a lightweight JSON spec:
```chart
{
"type": "bar",
"title": "Revenue Q1",
"x": ["Jan", "Feb", "Mar"],
"series": [
{ "name": "2025", "data": [120, 150, 170] },
{ "name": "2026", "data": [140, 160, 200] }
],
"options": { "stacked": false, "legend": true }
}
```
Supported types: bar, line, area, pie, scatter, radar.
A "View data" button opens a bottom sheet with the raw data table. Invalid JSON shows a clear error message.
Images #

- Remote images are fetched and cached via
cached_network_image. - A shimmer placeholder is shown while loading; a retry button on failure.
- Tap to open a full-screen
photo_viewhero with pinch zoom. - Alt text is used as the
Semanticslabel.
Safe links #
All links go through SafeLink.open(context, url):
- Only
http,https,mailto, andtelschemes are allowed. - Unknown domains show a confirmation dialog ("You are about to open: example.com") before launching.
You can wire this up directly:
MarkdownRenderer(
data: markdown,
onTapLink: (url) => SafeLink.open(context, url),
)
Extension API #
Register a custom fence-tag renderer without modifying the library:
class GeoMapExtension extends MarkdownExtension {
@override
String get fenceTag => 'geomap';
@override
Widget build(BuildContext context, String raw, MarkdownTheme theme) {
return GeoMapWidget(geoJson: raw);
}
}
MarkdownRenderer(
data: markdown,
extensions: [GeoMapExtension()],
)
Any ```geomap fence block is dispatched to your extension.
Accessibility #
- All text respects
MediaQuery.textScalerOf(context)for system font scaling. Semanticslabels on code blocks ("Code block, python, 10 lines"), tables, and images.- Token fade-in animation is disabled when
MediaQuery.disableAnimationsis true. - Colours meet WCAG AA contrast in both light and dark presets.
Performance #
| Metric | Target |
|---|---|
| Frame time during streaming (P95) | ≤ 16 ms |
| Cold render 200-block message | ≤ 250 ms |
| Memory for 50 KB message | ≤ 8 MB |
Key techniques:
- Stable block keys prevent unnecessary rebuilds during streaming.
RepaintBoundaryaround code, math, and chart blocks.- Streaming rebuilds are local via
ValueListenableBuilder, never at the root. - Images load lazily (only when within viewport ± 200 px).
API reference #
MarkdownRenderer #
| Parameter | Type | Default | Description |
|---|---|---|---|
data |
String |
required | Raw markdown string |
theme |
MarkdownTheme? |
null |
Override for MarkdownThemeScope |
selectable |
bool |
false |
Wrap content in SelectionArea |
streaming |
bool |
false |
Enable streaming render mode |
onTapLink |
void Function(String url)? |
null |
Link tap callback |
imageBuilder |
Widget Function(BuildContext, String src)? |
null |
Custom image widget |
extensions |
List<MarkdownExtension> |
[] |
Custom fence-tag renderers |
MarkdownTheme #
Static presets: MarkdownTheme.chatGptLight, MarkdownTheme.chatGptDark.
All fields are listed in the Theme tokens table above.
SafeLink #
static Future<void> open(BuildContext context, String url)
ClipboardHelper #
static Future<void> copyText(String text)
static Future<void> showCopySnackbar(BuildContext context, String message)
Example app #
The example/ directory contains a full demo app with:
- Demo screen — 25 sections showcasing every block type.
- Chat screen — simulated AI streaming with preset responses covering math, code, tables, chemistry, and biology.
Run it:
cd example
flutter run
Roadmap #
- ❌ Mermaid diagrams (WebView-based, opt-in dependency)
- ❌ Footnotes with popover
- ❌ RTL (Arabic, Hebrew) support
- ❌ Sticky table headers
- ❌ TOC / scroll-to-section
- ❌ HTML subset rendering (
<sub>,<sup>,<kbd>,<mark>)
License #
MIT — see LICENSE.
