
High-performance Markdown parser and renderer with AST-based incremental parsing, streaming (LLM) rendering, built-in image loading, LaTeX math, customizable themes, pagination, and broad CommonMark/GFM extensions support.
A Blazing-Fast, Cross-Platform Markdown Engine for Compose Multiplatform
One library. One codebase. Pixel-perfect Markdown on Android, iOS, Desktop & Web.
| Feature | Description | |
|---|---|---|
| π | Blazing Fast | AST-based recursive descent parser with incremental parsing β only re-parses what changed |
| π | True Cross-Platform | One codebase renders identically on Android, iOS, Desktop (JVM), Web (Wasm/JS) |
| π | 100% Coverage | 372 Markdown features, 652/652 CommonMark Spec tests passing, plus GFM & 20+ extensions |
| π€ | LLM-Ready Streaming | First-class token-by-token rendering with 5fps throttling β zero flicker during AI generation |
| π¨ | Fully Themeable | 30+ configurable properties, built-in GitHub light/dark themes, auto system detection |
| π | LaTeX Math | Inline $...$ and block $$...$$ formulas via integrated LaTeX rendering engine |
| π | Built-in Linting | 13+ diagnostic rules including WCAG accessibility checks β catch issues at parse time |
| πΌοΈ | Image Loading | Coil3 + Ktor3 out-of-the-box, with size specification and custom renderer support |
| π | Pagination | Progressive rendering for ultra-long documents (500+ blocks) with auto load-more |
Real-time token-by-token output with incremental parsing β no flicker, no re-render.
Built-in linting with WCAG accessibility checks β heading jumps, broken footnotes, empty links, and more.
Full HTML block/inline support, GFM tables, admonitions, math, code highlighting, and 20+ extensions.
Add to your gradle/libs.versions.toml:
[versions]
markdown = "1.0.3"
[libraries]
markdown-parser = { module = "io.github.huarangmeng:markdown-parser", version.ref = "markdown" }
markdown-renderer = { module = "io.github.huarangmeng:markdown-renderer", version.ref = "markdown" }Then in your module's build.gradle.kts:
dependencies {
implementation(libs.markdown.parser)
implementation(libs.markdown.renderer)
}π‘
markdown-rendererbundles Coil3 + Ktor3 for image loading as transitive dependencies.
import com.hrm.markdown.renderer.Markdown
import com.hrm.markdown.renderer.MarkdownTheme
@Composable
fun MyScreen() {
Markdown(
markdown = """
# Hello World
This is a paragraph with **bold** and *italic* text.
- Item 1
- Item 2
```kotlin
fun hello() = println("Hello")
```
""".trimIndent(),
modifier = Modifier.fillMaxSize(),
theme = MarkdownTheme.auto(), // Follows system light/dark mode
)
}That's it β 3 lines to render beautiful Markdown across all platforms.
Purpose-built for AI/LLM scenarios. Enable isStreaming for flicker-free incremental rendering:
var text by remember { mutableStateOf("") }
var isStreaming by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
llmTokenFlow.collect { token ->
text += token
}
isStreaming = false
}
Markdown(
markdown = text,
isStreaming = isStreaming, // Enables incremental parsing + 5fps throttled rendering
)What happens under the hood:
// Built-in themes
Markdown(markdown = text, theme = MarkdownTheme.light()) // GitHub Light
Markdown(markdown = text, theme = MarkdownTheme.dark()) // GitHub Dark
Markdown(markdown = text, theme = MarkdownTheme.auto()) // Auto-detect
// Full customization (30+ properties)
Markdown(
markdown = text,
theme = MarkdownTheme(
headingStyles = listOf(
TextStyle(fontSize = 32.sp, fontWeight = FontWeight.Bold),
// h2 ~ h6 ...
),
bodyStyle = TextStyle(fontSize = 16.sp),
codeBlockBackground = Color(0xFFF5F5F5),
// ...and much more
),
onLinkClick = { url -> /* handle click */ },
)| Feature | Details |
|---|---|
| Headings | ATX (# ~ ######), Setext (===/---), custom IDs ({#id}), auto-generated anchors |
| Paragraphs | Multi-line merging, blank line separation, lazy continuation |
| Code Blocks | Fenced (```/~~~) with language highlight (20+ languages), indented, line numbers, line highlighting |
| Block Quotes | Nested, lazy continuation, inner block elements |
| Lists | Unordered/ordered/task lists, nested, tight/loose distinction |
| Tables (GFM) | Column alignment, inline formatting in cells, escaped pipes |
| Thematic Breaks |
---, ***, ___
|
| HTML Blocks | All 7 CommonMark types |
| Link Reference Definitions | Full support with title variants |
| Feature | Details |
|---|---|
| Emphasis | Bold, italic, bold-italic, nested, CJK-aware delimiter rules |
| Strikethrough |
~~text~~, ~text~
|
| Inline Code | Single/multi backtick, space stripping |
| Links | Inline, reference (full/collapsed/shortcut), autolinks, GFM bare URLs, attribute blocks |
| Images | Inline, reference, =WxH size specification, attribute blocks, auto Figure conversion |
| Inline HTML | Tags, comments, CDATA, processing instructions |
| Escapes & Entities | 32 escapable characters, named/numeric HTML entities |
| Line Breaks | Hard (spaces/backslash), soft |
| Feature | Syntax |
|---|---|
| Math (LaTeX) |
$...$ inline, $$...$$ block, \tag{}, \ref{}
|
| Footnotes |
[^label] references, multi-line definitions, block content |
| Admonitions |
> [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION]
|
| Highlight | ==text== |
| Super/Subscript |
^text^, ~text~, <sup>, <sub>
|
| Insert Text | ++text++ |
| Emoji |
:smile: shortcodes (200+), ASCII emoticons (40+), custom mappings |
| Definition Lists | Term + : definition format |
| Front Matter | YAML (---) and TOML (+++) |
| TOC |
[TOC] with depth, exclude, ordering options |
| Custom Containers |
:::type with nesting, CSS classes, IDs |
| Diagram Blocks | Mermaid, PlantUML, Graphviz, and more |
| Multi-Column Layout |
:::columns with percentage/pixel widths |
| Tab Blocks |
=== "Tab Title" MkDocs Material style |
| Shortcodes |
{% tag args %}...{% endtag %} with positional/keyword args |
| Spoiler Text |
>!hidden text!< Discord/Reddit style |
| Wiki Links |
[[page]], [[page|display text]]
|
| Ruby Text |
{ζΌ’ε|γγγ} pronunciation annotations |
| Bibliography |
[@key] citations with [^bibliography] definitions |
| Block Attributes |
{.class #id key=value} kramdown/Pandoc style |
| Page Breaks |
***pagebreak*** for print/PDF export |
| Styled Text |
[text]{.red style="color:red"} inline CSS |
Enable once, catch problems everywhere:
val parser = MarkdownParser(enableLinting = true)
val document = parser.parse(markdown)
document.diagnostics.forEach { diagnostic ->
println("Line ${diagnostic.line}: [${diagnostic.severity}] ${diagnostic.message}")
}13+ diagnostic rules:
| Rule | Severity | Description |
|---|---|---|
| Heading level skip | h1 β h3 without h2 | |
| Duplicate heading ID | Multiple headings generate the same anchor | |
| Invalid footnote ref | β ERROR | Reference to undefined footnote |
| Unused footnote | Footnote defined but never referenced | |
| Empty link target |
[text]() with no URL |
|
| Missing alt text | Images without description | |
| Empty link text | Links invisible to screen readers | |
| Non-descriptive link | "click here", "read more" links | |
| Missing code language | βΉοΈ INFO | Fenced code without language tag |
| Table missing header | Screen readers need <th>
|
|
| Long alt text | Alt text > 125 characters |
Follows WCAG 2.1 AA standards for accessibility compliance.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Compose App β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β markdown-renderer β markdown-preview β
β AST β Compose UI β Interactive demo & showcase β
β Block/Inline renderers β Categorized feature browser β
β Theme system β β
βββββββββββββββββββββββββββββ€ β
β markdown-parser β
β Markdown β AST β
β Streaming / Incremental / Flavour system β
β Linting / Diagnostics / Post-processors β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Module | Description |
|---|---|
:markdown-parser |
Core parsing engine β Markdown string β AST. Streaming, incremental, multi-flavour. |
:markdown-renderer |
Rendering engine β AST β Compose UI. Theming, image loading, code highlighting. |
:markdown-preview |
Interactive showcase β categorized demo of all supported features. |
:composeApp |
Cross-platform demo app (Android/iOS/Desktop/Web). |
:androidApp |
Android-specific demo app. |
| Spec | Status |
|---|---|
| CommonMark 0.31.2 | 652/652 (100%) β |
| GFM 0.29 | Tables, task lists, strikethrough, autolinks β |
| Markdown Extra | Footnotes, definition lists, abbreviations, fenced code β |
Configure which syntax features to enable:
// Strict CommonMark β no extensions
val doc = MarkdownParser(CommonMarkFlavour).parse(input)
// GFM β CommonMark + tables, strikethrough, autolinks
val doc = MarkdownParser(GFMFlavour).parse(input)
// Extended (default) β everything enabled
val doc = MarkdownParser().parse(input)
// One-shot HTML rendering
val html = HtmlRenderer.renderMarkdown(input, flavour = CommonMarkFlavour)Built-in Coil3 handles images automatically. Need custom rendering? Easy:
Markdown(
markdown = markdownText,
imageContent = { data, modifier ->
// data.url, data.altText, data.width, data.height available
AsyncImage(
model = data.url,
contentDescription = data.altText,
modifier = modifier,
)
},
)Supports size specification in Markdown:

# Android
./gradlew :composeApp:assembleDebug
# Desktop (JVM)
./gradlew :composeApp:run
# Web (Wasm)
./gradlew :composeApp:wasmJsBrowserDevelopmentRun
# Web (JS)
./gradlew :composeApp:jsBrowserDevelopmentRun
# iOS β open iosApp/ in Xcode./gradlew :markdown-parser:jvmTest # Parser tests
./gradlew :markdown-renderer:jvmTest # Renderer tests
./gradlew jvmTest # All tests| # | Category | Coverage |
|---|---|---|
| 1 | Headings | 17/17 (100%) |
| 2 | Paragraphs | 5/5 (100%) |
| 3 | Code Blocks | 17/17 (100%) |
| 4 | Block Quotes | 8/8 (100%) |
| 5 | Lists | 20/20 (100%) |
| 6 | Thematic Breaks | 6/6 (100%) |
| 7 | Tables (GFM) | 11/11 (100%) |
| 8 | HTML Blocks | 10/10 (100%) |
| 9 | Link References | 12/12 (100%) |
| 10 | Block Extensions | 85/85 (100%) |
| 11 | Emphasis | 13/13 (100%) |
| 12 | Strikethrough | 4/4 (100%) |
| 13 | Inline Code | 8/8 (100%) |
| 14 | Links | 27/27 (100%) |
| 15 | Images | 17/17 (100%) |
| 16 | Inline HTML | 8/8 (100%) |
| 17 | Escapes & Entities | 10/10 (100%) |
| 18 | Line Breaks | 5/5 (100%) |
| 19 | Inline Extensions | 50/50 (100%) |
| 20 | Streaming Engine | 27/27 (100%) |
| 21 | Character & Encoding | 10/10 (100%) |
| 22 | HTML Generator | 12/12 (100%) |
| 23 | Linting / WCAG | 19/19 (100%) |
| 24 | Shortcodes | 8/8 (100%) |
| Total | 372/372 (100%) |
π Full details: PARSER_COVERAGE_ANALYSIS.md
MIT License Β· Copyright (c) 2026 huarangmeng
This project is licensed under the MIT License β see the LICENSE file for details.
A Blazing-Fast, Cross-Platform Markdown Engine for Compose Multiplatform
One library. One codebase. Pixel-perfect Markdown on Android, iOS, Desktop & Web.
| Feature | Description | |
|---|---|---|
| π | Blazing Fast | AST-based recursive descent parser with incremental parsing β only re-parses what changed |
| π | True Cross-Platform | One codebase renders identically on Android, iOS, Desktop (JVM), Web (Wasm/JS) |
| π | 100% Coverage | 372 Markdown features, 652/652 CommonMark Spec tests passing, plus GFM & 20+ extensions |
| π€ | LLM-Ready Streaming | First-class token-by-token rendering with 5fps throttling β zero flicker during AI generation |
| π¨ | Fully Themeable | 30+ configurable properties, built-in GitHub light/dark themes, auto system detection |
| π | LaTeX Math | Inline $...$ and block $$...$$ formulas via integrated LaTeX rendering engine |
| π | Built-in Linting | 13+ diagnostic rules including WCAG accessibility checks β catch issues at parse time |
| πΌοΈ | Image Loading | Coil3 + Ktor3 out-of-the-box, with size specification and custom renderer support |
| π | Pagination | Progressive rendering for ultra-long documents (500+ blocks) with auto load-more |
Real-time token-by-token output with incremental parsing β no flicker, no re-render.
Built-in linting with WCAG accessibility checks β heading jumps, broken footnotes, empty links, and more.
Full HTML block/inline support, GFM tables, admonitions, math, code highlighting, and 20+ extensions.
Add to your gradle/libs.versions.toml:
[versions]
markdown = "1.0.3"
[libraries]
markdown-parser = { module = "io.github.huarangmeng:markdown-parser", version.ref = "markdown" }
markdown-renderer = { module = "io.github.huarangmeng:markdown-renderer", version.ref = "markdown" }Then in your module's build.gradle.kts:
dependencies {
implementation(libs.markdown.parser)
implementation(libs.markdown.renderer)
}π‘
markdown-rendererbundles Coil3 + Ktor3 for image loading as transitive dependencies.
import com.hrm.markdown.renderer.Markdown
import com.hrm.markdown.renderer.MarkdownTheme
@Composable
fun MyScreen() {
Markdown(
markdown = """
# Hello World
This is a paragraph with **bold** and *italic* text.
- Item 1
- Item 2
```kotlin
fun hello() = println("Hello")
```
""".trimIndent(),
modifier = Modifier.fillMaxSize(),
theme = MarkdownTheme.auto(), // Follows system light/dark mode
)
}That's it β 3 lines to render beautiful Markdown across all platforms.
Purpose-built for AI/LLM scenarios. Enable isStreaming for flicker-free incremental rendering:
var text by remember { mutableStateOf("") }
var isStreaming by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
llmTokenFlow.collect { token ->
text += token
}
isStreaming = false
}
Markdown(
markdown = text,
isStreaming = isStreaming, // Enables incremental parsing + 5fps throttled rendering
)What happens under the hood:
// Built-in themes
Markdown(markdown = text, theme = MarkdownTheme.light()) // GitHub Light
Markdown(markdown = text, theme = MarkdownTheme.dark()) // GitHub Dark
Markdown(markdown = text, theme = MarkdownTheme.auto()) // Auto-detect
// Full customization (30+ properties)
Markdown(
markdown = text,
theme = MarkdownTheme(
headingStyles = listOf(
TextStyle(fontSize = 32.sp, fontWeight = FontWeight.Bold),
// h2 ~ h6 ...
),
bodyStyle = TextStyle(fontSize = 16.sp),
codeBlockBackground = Color(0xFFF5F5F5),
// ...and much more
),
onLinkClick = { url -> /* handle click */ },
)| Feature | Details |
|---|---|
| Headings | ATX (# ~ ######), Setext (===/---), custom IDs ({#id}), auto-generated anchors |
| Paragraphs | Multi-line merging, blank line separation, lazy continuation |
| Code Blocks | Fenced (```/~~~) with language highlight (20+ languages), indented, line numbers, line highlighting |
| Block Quotes | Nested, lazy continuation, inner block elements |
| Lists | Unordered/ordered/task lists, nested, tight/loose distinction |
| Tables (GFM) | Column alignment, inline formatting in cells, escaped pipes |
| Thematic Breaks |
---, ***, ___
|
| HTML Blocks | All 7 CommonMark types |
| Link Reference Definitions | Full support with title variants |
| Feature | Details |
|---|---|
| Emphasis | Bold, italic, bold-italic, nested, CJK-aware delimiter rules |
| Strikethrough |
~~text~~, ~text~
|
| Inline Code | Single/multi backtick, space stripping |
| Links | Inline, reference (full/collapsed/shortcut), autolinks, GFM bare URLs, attribute blocks |
| Images | Inline, reference, =WxH size specification, attribute blocks, auto Figure conversion |
| Inline HTML | Tags, comments, CDATA, processing instructions |
| Escapes & Entities | 32 escapable characters, named/numeric HTML entities |
| Line Breaks | Hard (spaces/backslash), soft |
| Feature | Syntax |
|---|---|
| Math (LaTeX) |
$...$ inline, $$...$$ block, \tag{}, \ref{}
|
| Footnotes |
[^label] references, multi-line definitions, block content |
| Admonitions |
> [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION]
|
| Highlight | ==text== |
| Super/Subscript |
^text^, ~text~, <sup>, <sub>
|
| Insert Text | ++text++ |
| Emoji |
:smile: shortcodes (200+), ASCII emoticons (40+), custom mappings |
| Definition Lists | Term + : definition format |
| Front Matter | YAML (---) and TOML (+++) |
| TOC |
[TOC] with depth, exclude, ordering options |
| Custom Containers |
:::type with nesting, CSS classes, IDs |
| Diagram Blocks | Mermaid, PlantUML, Graphviz, and more |
| Multi-Column Layout |
:::columns with percentage/pixel widths |
| Tab Blocks |
=== "Tab Title" MkDocs Material style |
| Shortcodes |
{% tag args %}...{% endtag %} with positional/keyword args |
| Spoiler Text |
>!hidden text!< Discord/Reddit style |
| Wiki Links |
[[page]], [[page|display text]]
|
| Ruby Text |
{ζΌ’ε|γγγ} pronunciation annotations |
| Bibliography |
[@key] citations with [^bibliography] definitions |
| Block Attributes |
{.class #id key=value} kramdown/Pandoc style |
| Page Breaks |
***pagebreak*** for print/PDF export |
| Styled Text |
[text]{.red style="color:red"} inline CSS |
Enable once, catch problems everywhere:
val parser = MarkdownParser(enableLinting = true)
val document = parser.parse(markdown)
document.diagnostics.forEach { diagnostic ->
println("Line ${diagnostic.line}: [${diagnostic.severity}] ${diagnostic.message}")
}13+ diagnostic rules:
| Rule | Severity | Description |
|---|---|---|
| Heading level skip | h1 β h3 without h2 | |
| Duplicate heading ID | Multiple headings generate the same anchor | |
| Invalid footnote ref | β ERROR | Reference to undefined footnote |
| Unused footnote | Footnote defined but never referenced | |
| Empty link target |
[text]() with no URL |
|
| Missing alt text | Images without description | |
| Empty link text | Links invisible to screen readers | |
| Non-descriptive link | "click here", "read more" links | |
| Missing code language | βΉοΈ INFO | Fenced code without language tag |
| Table missing header | Screen readers need <th>
|
|
| Long alt text | Alt text > 125 characters |
Follows WCAG 2.1 AA standards for accessibility compliance.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Compose App β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β markdown-renderer β markdown-preview β
β AST β Compose UI β Interactive demo & showcase β
β Block/Inline renderers β Categorized feature browser β
β Theme system β β
βββββββββββββββββββββββββββββ€ β
β markdown-parser β
β Markdown β AST β
β Streaming / Incremental / Flavour system β
β Linting / Diagnostics / Post-processors β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Module | Description |
|---|---|
:markdown-parser |
Core parsing engine β Markdown string β AST. Streaming, incremental, multi-flavour. |
:markdown-renderer |
Rendering engine β AST β Compose UI. Theming, image loading, code highlighting. |
:markdown-preview |
Interactive showcase β categorized demo of all supported features. |
:composeApp |
Cross-platform demo app (Android/iOS/Desktop/Web). |
:androidApp |
Android-specific demo app. |
| Spec | Status |
|---|---|
| CommonMark 0.31.2 | 652/652 (100%) β |
| GFM 0.29 | Tables, task lists, strikethrough, autolinks β |
| Markdown Extra | Footnotes, definition lists, abbreviations, fenced code β |
Configure which syntax features to enable:
// Strict CommonMark β no extensions
val doc = MarkdownParser(CommonMarkFlavour).parse(input)
// GFM β CommonMark + tables, strikethrough, autolinks
val doc = MarkdownParser(GFMFlavour).parse(input)
// Extended (default) β everything enabled
val doc = MarkdownParser().parse(input)
// One-shot HTML rendering
val html = HtmlRenderer.renderMarkdown(input, flavour = CommonMarkFlavour)Built-in Coil3 handles images automatically. Need custom rendering? Easy:
Markdown(
markdown = markdownText,
imageContent = { data, modifier ->
// data.url, data.altText, data.width, data.height available
AsyncImage(
model = data.url,
contentDescription = data.altText,
modifier = modifier,
)
},
)Supports size specification in Markdown:

# Android
./gradlew :composeApp:assembleDebug
# Desktop (JVM)
./gradlew :composeApp:run
# Web (Wasm)
./gradlew :composeApp:wasmJsBrowserDevelopmentRun
# Web (JS)
./gradlew :composeApp:jsBrowserDevelopmentRun
# iOS β open iosApp/ in Xcode./gradlew :markdown-parser:jvmTest # Parser tests
./gradlew :markdown-renderer:jvmTest # Renderer tests
./gradlew jvmTest # All tests| # | Category | Coverage |
|---|---|---|
| 1 | Headings | 17/17 (100%) |
| 2 | Paragraphs | 5/5 (100%) |
| 3 | Code Blocks | 17/17 (100%) |
| 4 | Block Quotes | 8/8 (100%) |
| 5 | Lists | 20/20 (100%) |
| 6 | Thematic Breaks | 6/6 (100%) |
| 7 | Tables (GFM) | 11/11 (100%) |
| 8 | HTML Blocks | 10/10 (100%) |
| 9 | Link References | 12/12 (100%) |
| 10 | Block Extensions | 85/85 (100%) |
| 11 | Emphasis | 13/13 (100%) |
| 12 | Strikethrough | 4/4 (100%) |
| 13 | Inline Code | 8/8 (100%) |
| 14 | Links | 27/27 (100%) |
| 15 | Images | 17/17 (100%) |
| 16 | Inline HTML | 8/8 (100%) |
| 17 | Escapes & Entities | 10/10 (100%) |
| 18 | Line Breaks | 5/5 (100%) |
| 19 | Inline Extensions | 50/50 (100%) |
| 20 | Streaming Engine | 27/27 (100%) |
| 21 | Character & Encoding | 10/10 (100%) |
| 22 | HTML Generator | 12/12 (100%) |
| 23 | Linting / WCAG | 19/19 (100%) |
| 24 | Shortcodes | 8/8 (100%) |
| Total | 372/372 (100%) |
π Full details: PARSER_COVERAGE_ANALYSIS.md
MIT License Β· Copyright (c) 2026 huarangmeng
This project is licensed under the MIT License β see the LICENSE file for details.