
Declarative Compose Markdown renderer using CommonMark parsing, modular plugin architecture for tables/images/HTML, themeable typography/colors, and extensible parser extensions for customizable rendering.
A Compose Multiplatform Markdown rendering library that supports Android, iOS, Desktop (JVM), and WebAssembly (Wasm).
Looking for Android-only with richer Markdown compatibility? Check out ComposeMarkdown — it offers deeper Markdown spec support (powered by Flexmark) and more rendering features for Android projects.
| Desktop | Android | WebAssembly (Wasm) |
|---|---|---|
![]() |
![]() |
![]() |
commonmark-kotlin parser (pure Kotlin Multiplatform)Text with maxLines / overflow support and cross-paragraph text selectionMarkdownView and MarkdownText overloads accept a parsing dispatcher with loading/error content| Platform | Status |
|---|---|
| Android | Supported |
| iOS (arm64, x64, simulator) | Supported |
| Desktop (JVM) | Supported |
| WebAssembly (Wasm) | Supported |
Add the dependency to your project's build.gradle.kts:
// In your shared module's build.gradle.kts
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.feiyin0719:markdown-multiplatform:<version>")
}
}
}
}| Plugin | Artifact | Description |
|---|---|---|
| Table | markdown-multiplatform-table |
GFM table support |
| Image | markdown-multiplatform-image |
Markdown image rendering |
| HTML | markdown-multiplatform-html |
HTML inline tag support |
dependencies {
implementation("io.github.feiyin0719:markdown-multiplatform-table:<version>")
implementation("io.github.feiyin0719:markdown-multiplatform-image:<version>")
implementation("io.github.feiyin0719:markdown-multiplatform-html:<version>")
}import io.github.feiyin0719.markdown.multiplatform.MarkdownView
@Composable
fun SimpleMarkdownExample() {
val markdownContent = """
# Hello Compose Markdown Multiplatform
This is a **cross-platform** Markdown rendering library.
- Android
- iOS
- Desktop
- Web (Wasm)
""".trimIndent()
MarkdownView(
content = markdownContent,
modifier = Modifier.fillMaxSize(),
)
}LazyMarkdownView reads a stable line source incrementally and recycles parsed nodes away from the
viewport. Stable lazy-item keys preserve the visible anchor when nodes are removed or reloaded.
It can also accept a Markdown string directly for side-by-side comparison with the Android API.
@Composable
fun LargeMarkdownExample(markdown: String) {
val source = remember(markdown) { StringMarkdownLineSource(markdown) }
LazyMarkdownView(
source = source,
modifier = Modifier.fillMaxSize(),
chunkLoaderConfig = MarkdownChunkLoaderConfig(
initialLineCount = 1000,
incrementalLineCount = 500,
minNodesAhead = 100,
minNodesBehind = 30,
maxCachedNodes = 500,
maxCachedSourceLines = 10_000,
),
)
}For true lazy I/O, implement MarkdownLineSource with a stable file, asset, database, or range API.
The source must support rereading old ranges because scrolling backward reloads recycled nodes.
Chunk parses do not share reference-definition state; use inline links or LazyMarkdownColumn when
full-document reference resolution is required.
onLoadingChanged reports only the initial empty-screen wait. Use onStateChanged with
LazyMarkdownViewState to observe optional background before/after loading UI. AST recycling is
silent and internal concurrency uses a separate operation guard.
maxCachedSourceLines is also a hard limit for one unconfirmed trailing block or source context.
Set isStreaming = true on MarkdownView or MarkdownText while text is appended at the end.
Stable prefix nodes are reused and only the previous final block is reparsed. Set
isStreaming = false when generation finishes to force an authoritative full parse. A custom
StreamingMarkdownParser can replace the default workflow through
MarkdownRenderConfig.Builder.streamingMarkdownParserFactory; its parse method receives only
the complete content and streaming flag and can control the entire parsing lifecycle. The factory
defaults to null; without one, streaming requests use normal full parsing. Configure
::DefaultStreamingMarkdownParser explicitly to enable the built-in workflow. It creates its own
parser with at least block source spans. Regular parser spans are configurable and default to
IncludeSourceSpans.BLOCKS; LazyMarkdownView also forces at least BLOCKS.
A custom streaming parser must return a new root Document for each changed input so Compose sees
the update, while reusing unchanged completed child blocks by identity so their keyed renderers can
skip recomposition.
MarkdownView(
text = streamedMarkdown,
parseDispatcher = Dispatchers.Default,
isStreaming = streamInProgress,
)| Technology | Purpose |
|---|---|
| Compose Multiplatform | Cross-platform UI framework |
| commonmark-kotlin | Markdown parsing engine (pure Kotlin Multiplatform) |
| Kotlin Coroutines | Asynchronous processing |
| Material Design 3 | Design language specification |
For full API signatures and detailed parameter explanations, see the dedicated API document:
We welcome contributions! To get started:
git checkout -b feat/my-feature
./gradlew ktlintFormat
./gradlew ktlintCheck
./gradlew assemble
feat:, fix:, docs:, etc.)Released under the MIT License. See LICENSE for details.
Made with love by the Compose Markdown team
A Compose Multiplatform Markdown rendering library that supports Android, iOS, Desktop (JVM), and WebAssembly (Wasm).
Looking for Android-only with richer Markdown compatibility? Check out ComposeMarkdown — it offers deeper Markdown spec support (powered by Flexmark) and more rendering features for Android projects.
| Desktop | Android | WebAssembly (Wasm) |
|---|---|---|
![]() |
![]() |
![]() |
commonmark-kotlin parser (pure Kotlin Multiplatform)Text with maxLines / overflow support and cross-paragraph text selectionMarkdownView and MarkdownText overloads accept a parsing dispatcher with loading/error content| Platform | Status |
|---|---|
| Android | Supported |
| iOS (arm64, x64, simulator) | Supported |
| Desktop (JVM) | Supported |
| WebAssembly (Wasm) | Supported |
Add the dependency to your project's build.gradle.kts:
// In your shared module's build.gradle.kts
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.feiyin0719:markdown-multiplatform:<version>")
}
}
}
}| Plugin | Artifact | Description |
|---|---|---|
| Table | markdown-multiplatform-table |
GFM table support |
| Image | markdown-multiplatform-image |
Markdown image rendering |
| HTML | markdown-multiplatform-html |
HTML inline tag support |
dependencies {
implementation("io.github.feiyin0719:markdown-multiplatform-table:<version>")
implementation("io.github.feiyin0719:markdown-multiplatform-image:<version>")
implementation("io.github.feiyin0719:markdown-multiplatform-html:<version>")
}import io.github.feiyin0719.markdown.multiplatform.MarkdownView
@Composable
fun SimpleMarkdownExample() {
val markdownContent = """
# Hello Compose Markdown Multiplatform
This is a **cross-platform** Markdown rendering library.
- Android
- iOS
- Desktop
- Web (Wasm)
""".trimIndent()
MarkdownView(
content = markdownContent,
modifier = Modifier.fillMaxSize(),
)
}LazyMarkdownView reads a stable line source incrementally and recycles parsed nodes away from the
viewport. Stable lazy-item keys preserve the visible anchor when nodes are removed or reloaded.
It can also accept a Markdown string directly for side-by-side comparison with the Android API.
@Composable
fun LargeMarkdownExample(markdown: String) {
val source = remember(markdown) { StringMarkdownLineSource(markdown) }
LazyMarkdownView(
source = source,
modifier = Modifier.fillMaxSize(),
chunkLoaderConfig = MarkdownChunkLoaderConfig(
initialLineCount = 1000,
incrementalLineCount = 500,
minNodesAhead = 100,
minNodesBehind = 30,
maxCachedNodes = 500,
maxCachedSourceLines = 10_000,
),
)
}For true lazy I/O, implement MarkdownLineSource with a stable file, asset, database, or range API.
The source must support rereading old ranges because scrolling backward reloads recycled nodes.
Chunk parses do not share reference-definition state; use inline links or LazyMarkdownColumn when
full-document reference resolution is required.
onLoadingChanged reports only the initial empty-screen wait. Use onStateChanged with
LazyMarkdownViewState to observe optional background before/after loading UI. AST recycling is
silent and internal concurrency uses a separate operation guard.
maxCachedSourceLines is also a hard limit for one unconfirmed trailing block or source context.
Set isStreaming = true on MarkdownView or MarkdownText while text is appended at the end.
Stable prefix nodes are reused and only the previous final block is reparsed. Set
isStreaming = false when generation finishes to force an authoritative full parse. A custom
StreamingMarkdownParser can replace the default workflow through
MarkdownRenderConfig.Builder.streamingMarkdownParserFactory; its parse method receives only
the complete content and streaming flag and can control the entire parsing lifecycle. The factory
defaults to null; without one, streaming requests use normal full parsing. Configure
::DefaultStreamingMarkdownParser explicitly to enable the built-in workflow. It creates its own
parser with at least block source spans. Regular parser spans are configurable and default to
IncludeSourceSpans.BLOCKS; LazyMarkdownView also forces at least BLOCKS.
A custom streaming parser must return a new root Document for each changed input so Compose sees
the update, while reusing unchanged completed child blocks by identity so their keyed renderers can
skip recomposition.
MarkdownView(
text = streamedMarkdown,
parseDispatcher = Dispatchers.Default,
isStreaming = streamInProgress,
)| Technology | Purpose |
|---|---|
| Compose Multiplatform | Cross-platform UI framework |
| commonmark-kotlin | Markdown parsing engine (pure Kotlin Multiplatform) |
| Kotlin Coroutines | Asynchronous processing |
| Material Design 3 | Design language specification |
For full API signatures and detailed parameter explanations, see the dedicated API document:
We welcome contributions! To get started:
git checkout -b feat/my-feature
./gradlew ktlintFormat
./gradlew ktlintCheck
./gradlew assemble
feat:, fix:, docs:, etc.)Released under the MIT License. See LICENSE for details.
Made with love by the Compose Markdown team