# multiplatform-markdown-renderer parses Markdown into Compose composables

> This Kotlin Multiplatform library renders Markdown as native Compose UI, parsing the text with JetBrains Markdown into an AST and mapping each node to a composable you can override. The default branch is develop, the artifact version in the README is behind the current tag, and the module split means you only depend on the theme variant you use.

**mikepenz/multiplatform-markdown-renderer** — Markdown renderer for Kotlin Multiplatform Projects (Android, iOS, Desktop), using Compose.

- Repository: https://github.com/mikepenz/multiplatform-markdown-renderer
- Website: https://mikepenz.github.io/multiplatform-markdown-renderer/
- Stars: 1,119 · Forks: 88
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mikepenz-multiplatform-markdown-renderer

## Markdown becomes Compose nodes, not HTML

The mechanism is a three stage pipeline, and the hero diagram in the README states it in one line: a markdown String is parsed by JetBrains Markdown into an AST, mapped through overridable MarkdownComponents, and rendered as Compose Multiplatform UI on Android, iOS, Desktop, Web and macOS. So there is no intermediate HTML and no WebView anywhere in the chain. That distinction is the whole point of the library, because a WebView based markdown renderer gives you a scrollable island inside your app that does not match your theme, does not respect your typography scale and cannot be made to feel native. Here, a heading is a Compose text composable with your typography, and a table is your own table composable. The cost is that you have to be comfortable with the AST, which is a Kotlin Multiplatform data model rather than a standard.

## One commonMain call, five targets

The platform claim is the first row of the feature table and it is specific: Android, iOS, Desktop on the JVM, Web on Wasm and JS, and macOS, all from one call in `commonMain`. The topic tags cover android, compose, jvm, kotlin, kotlin-multiplatform and markdown. The repository layout matches that promise, with a `sample/` directory containing separate `android/`, `desktop/`, `ios/`, `shared/` and `web/` subdirectories, so the sample is itself a multiplatform project rather than a set of independent demos. The artifact is published on Maven Central under the `com.mikepenz` group, and the licence is Apache 2.0. The last push to the develop branch was on 2026-10-13, the same day as the v0.46.0 release, and the previous tags are 0.45.0 from 2026-08-28 and 0.44.0 from 2026-08-18.

## The theme variant is a separate artifact

The dependency step shows a design choice that will save you a conflict later. There are two modules, one for Material 2 and one for Material 3, and you pick the one matching your theme:

```kotlin
dependencies {
    implementation("com.mikepenz:multiplatform-markdown-renderer:0.43.0")
    implementation("com.mikepenz:multiplatform-markdown-renderer-m3:0.43.0") // or -m2
}
```

The base module carries the shared logic and the theme module carries the defaults, and the themed defaults can be overridden with `markdownColor()` and `markdownTypography()`. Shipping two theme variants as separate artifacts rather than one artifact with two variants avoids the situation where a Material 3 app pulls in Material 2 defaults by accident, and it means the colour and typography code for each theme evolves separately. The import path reflects the same split, with `Markdown` coming from `com.mikepenz.markdown.m3` or `com.mikepenz.markdown.m2`.

## Parsing happens off the composition

The async behaviour is what makes the library usable on a real content screen. `rememberMarkdownState` parses off the composition, and `retainState = true` keeps the content visible while re-parsing. The alternative would be parsing inside a composable, which blocks the main thread and drops frames. The quickstart shows the hoisted form, which is the pattern to use for anything non-trivial:

```kotlin
val markdownState = rememberMarkdownState(markdown)
Markdown(markdownState)
```

and the simplest form for a string literal is a direct call, where the composable takes the text itself:

```kotlin
import com.mikepenz.markdown.m3.Markdown

Markdown(
    """
    # Hello Markdown

    - Bullet points
    - **Bold** and *italic* text

    [Check out this link](https://github.com/mikepenz/multiplatform-markdown-renderer)
    """.trimIndent()
)
```

The `retainState` flag is the interesting part of the contract, because it defines what the user sees during a re-parse. Without it, the content disappears while the new version is parsed, which on a long document is a visible flash.

## Streaming appends chunks and re-parses only the tail

The streaming support is aimed squarely at LLM output. `rememberStreamingMarkdownState()` appends chunks and re-parses only the unstable tail, which is the difference between a chat UI that stays smooth while a model is generating and one that re-parses the whole transcript on every token. The reference section lists streaming as a topic alongside setup, usage and image loading, so it is a first-class part of the API rather than an example. The `commonMain` promise matters here as well, since the same streaming state has to work when the chunks arrive from an Android network call and from a WebSocket in a desktop app. The repository also has a `multiplatform-markdown-renderer-code/` module and Coil integration modules, `multiplatform-markdown-renderer-coil2/` and `-coil3/`, which are how images referenced in Markdown get loaded on each platform.

## Every AST node maps to a composable you own

The extension mechanism is `MarkdownComponents`, which maps each AST node to a `@Composable` you control, and the feature table lists every element as overridable. The showcase images show what that means in practice. The default `Markdown` composable with no configuration renders headings, italic, bold, inline code, links, blockquotes and ordered plus unordered lists. Wiring `markdownComponents(codeFence = ...)` to `MarkdownHighlightedCodeFence` from the `-code` module gives you syntax-highlighted code blocks with a language header and a copy button. The custom panel shows task list checkboxes rendered by a custom component, and `markdownColor()` together with `markdownExtendedSpans` and `RoundedCornerSpanPainter` producing inline code spans with rounded backgrounds. GFM tables and GitHub alerts, with NOTE and WARNING banners carrying accent bars and icons, render without any opt-in, along with tables, task lists, strikethrough and autolinks.

## The showcase images are generated, not hand-made

The screenshots in the README are Paparazzi snapshots of the sample app, recorded from a file called `ReadmeShowcasePreviews.kt` and refreshed by a gradle command that records the Paparazzi images and copies them into the readme art directory. That is worth noting for two reasons. First, the documentation cannot drift from the code, because the images are regenerated by a task rather than captured by hand. Second, it tells you the project cares about visual regression, which for a rendering library is the main risk: a change to a default component can alter spacing or colour in every screen that uses it, and Paparazzi is how that gets caught. The repository also has a `renovate.json` for dependency updates, a `SECURITY.md`, a `CONTRIBUTORS.md` and a `CHANGELOG.md`, and the README links a MIGRATION.md for people upgrading between versions.

## Conclusion

Adopt multiplatform-markdown-renderer if you are building a Compose Multiplatform app that has to show Markdown, since the same call works on every target and the AST mapping means you can restyle any element without forking the library. Do not adopt it for a non-Compose UI, and be careful with the version you pick, since the README's quickstart still references 0.43.0 while the current tag is 0.46.0. Verify first which Material variant you need, because the theme module is a separate dependency, and check the MIGRATION.md file before upgrading, since the project keeps one and the API has moved between versions.

## FAQ

### How does multiplatform-markdown-renderer render Markdown?

The text is parsed by JetBrains Markdown into an AST, the AST is mapped through overridable MarkdownComponents, and the result is rendered as Compose Multiplatform UI. There is no HTML or WebView in the chain.

### Which platforms does multiplatform-markdown-renderer support?

Android, iOS, Desktop on the JVM, Web on Wasm and JS, and macOS, all from one call in commonMain. The sample project has matching android, desktop, ios, shared and web directories.

### How do I add multiplatform-markdown-renderer to a project?

Add the base module and the theme module matching your Material version, for example `com.mikepenz:multiplatform-markdown-renderer` and `com.mikepenz:multiplatform-markdown-renderer-m3`, then import `Markdown` from `com.mikepenz.markdown.m3` or `.m2` and call it with your text.

### What does rememberMarkdownState do in multiplatform-markdown-renderer?

It parses the Markdown asynchronously off the composition and survives recomposition, so long documents do not block the main thread. Setting `retainState = true` keeps the existing content visible while a re-parse happens instead of blanking the screen.

### Does multiplatform-markdown-renderer support streaming output?

Yes. `rememberStreamingMarkdownState()` appends chunks as they arrive and re-parses only the unstable tail, which is what keeps a chat UI smooth while a model is generating.

## Sources

- [License: Apache-2.0](https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/LICENSE)
- [mikepenz/multiplatform-markdown-renderer on GitHub](https://github.com/mikepenz/multiplatform-markdown-renderer)
- [Project website](https://mikepenz.github.io/multiplatform-markdown-renderer/)
- [README](https://github.com/mikepenz/multiplatform-markdown-renderer/blob/develop/README.md)
- [Releases](https://github.com/mikepenz/multiplatform-markdown-renderer/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mikepenz-multiplatform-markdown-renderer
