
Rich-text editor built on an immutable ProseMirror document as single source of truth, flattened rendering, diff→transaction edit pipeline, markdown/html clipboard, mentions, lists, undo/redo, inline images.
A Kotlin Multiplatform rich-text editor library for Compose Multiplatform, built on ProseMirror through its Kotlin port (com.atlassian.prosemirror).
The ProseMirror document model is the single source of truth — Compose renders a flattened projection of the immutable tree, and every edit flows back through a diff → transaction pipeline.
Platforms: Android · Desktop (JVM) · iOS · Web (scaffolding only)
Node tree as source of truth, with AnnotatedString + span styles as the flat rendering projectionSlice pipelinesetMarkdown, insertMarkdown, toMarkdown (JetBrains markdown/GFM parser → HTML → ProseMirror DOM parser)@mention-style triggers with token nodes inserted into the documentImageLoader
ProseMirrorEditor (filled), OutlinedProseMirrorEditor, read-only ProseMirrorText, expandable variants@ExperimentalProseMirrorApi / @InternalProseMirrorApi on the public API surface| Platform | Status |
|---|---|
| Android (minSdk 24) | ✅ |
| Desktop (JVM) | ✅ |
iOS (static framework ComposeApp) |
✅ |
| Web (JS/Wasm) | 🚧 scaffolding only — targets are commented out in the core module; the web apps currently run a placeholder UI |
Following the recommended Kotlin Multiplatform structure:
| Module | Role |
|---|---|
prosemirror-compose |
Editor core library |
prosemirror-compose-coil3 |
Optional Coil3 ImageLoader integration |
composeApp |
Shared Compose UI and demo App() consumed by the platform entry points |
androidApp / desktopApp / webApp / iosApp
|
Platform entry points |
androidApp ─┐
desktopApp ─┼──▶ composeApp ──▶ prosemirror-compose
iosApp ─────┘
webApp ──────┘ (once web targets are enabled)
prosemirror-compose-coil3 ──▶ prosemirror-compose
The editor keeps an immutable ProseMirror document tree as its state of truth. Compose never renders the tree directly:
ProseMirrorState holds the PMEditorState, the derived flat AnnotatedString, and the BasicTextField state.PositionCoordinateMap maps between ProseMirror structural positions and flat Compose text indices, in both directions.TextField → diff → ProseMirror Transaction → plugins/steps → rebuilt AnnotatedString; selection moves and IME composition take the inverse path.See CLAUDE.md for the full detail on the edit pipeline and its invariants.
androidApp, desktopApp, iosApp and webApp all run the same demo App() composable from composeApp, ported from the upstream compose-rich-editor sample: Home, rich editor, HTML editor, Markdown editor, Slack, Mentions, Undo/Redo, Lists config, real examples, Links, Images, GitHub, Notion, Headings, Claude, and Expandable text.
Requirements: JDK 17+, Android SDK (Android targets only).
# Android debug APK
./gradlew :androidApp:assembleDebug
# Desktop app
./gradlew :desktopApp:run # standard run
./gradlew :desktopApp:hotRun --auto # hot reload
# Web (placeholder UI only)
./gradlew :webApp:wasmJsBrowserDevelopmentRun
./gradlew :webApp:jsBrowserDevelopmentRun
# iOS — open iosApp/ in Xcode and run from there (iOS targets build on macOS only)Tests:
./gradlew :prosemirror-compose:jvmTest # primary suite (Desktop/JVM)
./gradlew :prosemirror-compose:testAndroidHostTest # Android host
./gradlew :prosemirror-compose:iosSimulatorArm64Test # iOS simulator (macOS only)The library modules are published to Maven Central under the io.github.woods-marshes group:
// In a Kotlin Multiplatform commonMain source set
dependencies {
implementation("io.github.woods-marshes:prosemirror-compose:0.1.0")
implementation("io.github.woods-marshes:prosemirror-compose-coil3:0.1.0") // optional Coil3 images
}Gradle module metadata is published for the kotlinMultiplatform artifact, so Android / JVM / iOS variants are resolved automatically from the same coordinates.
Publishing is driven by GitHub Actions through the Maven Central Portal:
main → publishes 0.1.0-SNAPSHOT to the Central Portal snapshot repository.v* tag (for example v0.1.0) → uploads 0.1.0 to the Central Portal and automatically publishes it after validation.Required GitHub Actions secrets:
| Secret | Purpose |
|---|---|
MAVEN_CENTRAL_USERNAME / MAVEN_CENTRAL_PASSWORD
|
Central Portal user token (generate it on central.sonatype.com after registering and claiming the io.github.woods-marshes namespace) |
SIGNING_KEY_ID |
Last 8 characters of the GPG public key ID |
SIGNING_KEY |
ASCII-armored GPG private key |
SIGNING_PASSWORD |
GPG private key passphrase |
Local publishing for smoke-testing:
./gradlew :prosemirror-compose:publishToMavenLocal :prosemirror-compose-coil3:publishToMavenLocalApache License 2.0 — see LICENSE.
This project is adapted from compose-rich-editor (Apache-2.0, © Mohamed Rejeb) and builds on the Kotlin port of ProseMirror, prosemirror-kotlin / com.atlassian.prosemirror:* (v1.1.20, Apache-2.0, © Atlassian Pty Ltd). The editor is re-packaged under com.github.wood.prosemirror.compose.
A Kotlin Multiplatform rich-text editor library for Compose Multiplatform, built on ProseMirror through its Kotlin port (com.atlassian.prosemirror).
The ProseMirror document model is the single source of truth — Compose renders a flattened projection of the immutable tree, and every edit flows back through a diff → transaction pipeline.
Platforms: Android · Desktop (JVM) · iOS · Web (scaffolding only)
Node tree as source of truth, with AnnotatedString + span styles as the flat rendering projectionSlice pipelinesetMarkdown, insertMarkdown, toMarkdown (JetBrains markdown/GFM parser → HTML → ProseMirror DOM parser)@mention-style triggers with token nodes inserted into the documentImageLoader
ProseMirrorEditor (filled), OutlinedProseMirrorEditor, read-only ProseMirrorText, expandable variants@ExperimentalProseMirrorApi / @InternalProseMirrorApi on the public API surface| Platform | Status |
|---|---|
| Android (minSdk 24) | ✅ |
| Desktop (JVM) | ✅ |
iOS (static framework ComposeApp) |
✅ |
| Web (JS/Wasm) | 🚧 scaffolding only — targets are commented out in the core module; the web apps currently run a placeholder UI |
Following the recommended Kotlin Multiplatform structure:
| Module | Role |
|---|---|
prosemirror-compose |
Editor core library |
prosemirror-compose-coil3 |
Optional Coil3 ImageLoader integration |
composeApp |
Shared Compose UI and demo App() consumed by the platform entry points |
androidApp / desktopApp / webApp / iosApp
|
Platform entry points |
androidApp ─┐
desktopApp ─┼──▶ composeApp ──▶ prosemirror-compose
iosApp ─────┘
webApp ──────┘ (once web targets are enabled)
prosemirror-compose-coil3 ──▶ prosemirror-compose
The editor keeps an immutable ProseMirror document tree as its state of truth. Compose never renders the tree directly:
ProseMirrorState holds the PMEditorState, the derived flat AnnotatedString, and the BasicTextField state.PositionCoordinateMap maps between ProseMirror structural positions and flat Compose text indices, in both directions.TextField → diff → ProseMirror Transaction → plugins/steps → rebuilt AnnotatedString; selection moves and IME composition take the inverse path.See CLAUDE.md for the full detail on the edit pipeline and its invariants.
androidApp, desktopApp, iosApp and webApp all run the same demo App() composable from composeApp, ported from the upstream compose-rich-editor sample: Home, rich editor, HTML editor, Markdown editor, Slack, Mentions, Undo/Redo, Lists config, real examples, Links, Images, GitHub, Notion, Headings, Claude, and Expandable text.
Requirements: JDK 17+, Android SDK (Android targets only).
# Android debug APK
./gradlew :androidApp:assembleDebug
# Desktop app
./gradlew :desktopApp:run # standard run
./gradlew :desktopApp:hotRun --auto # hot reload
# Web (placeholder UI only)
./gradlew :webApp:wasmJsBrowserDevelopmentRun
./gradlew :webApp:jsBrowserDevelopmentRun
# iOS — open iosApp/ in Xcode and run from there (iOS targets build on macOS only)Tests:
./gradlew :prosemirror-compose:jvmTest # primary suite (Desktop/JVM)
./gradlew :prosemirror-compose:testAndroidHostTest # Android host
./gradlew :prosemirror-compose:iosSimulatorArm64Test # iOS simulator (macOS only)The library modules are published to Maven Central under the io.github.woods-marshes group:
// In a Kotlin Multiplatform commonMain source set
dependencies {
implementation("io.github.woods-marshes:prosemirror-compose:0.1.0")
implementation("io.github.woods-marshes:prosemirror-compose-coil3:0.1.0") // optional Coil3 images
}Gradle module metadata is published for the kotlinMultiplatform artifact, so Android / JVM / iOS variants are resolved automatically from the same coordinates.
Publishing is driven by GitHub Actions through the Maven Central Portal:
main → publishes 0.1.0-SNAPSHOT to the Central Portal snapshot repository.v* tag (for example v0.1.0) → uploads 0.1.0 to the Central Portal and automatically publishes it after validation.Required GitHub Actions secrets:
| Secret | Purpose |
|---|---|
MAVEN_CENTRAL_USERNAME / MAVEN_CENTRAL_PASSWORD
|
Central Portal user token (generate it on central.sonatype.com after registering and claiming the io.github.woods-marshes namespace) |
SIGNING_KEY_ID |
Last 8 characters of the GPG public key ID |
SIGNING_KEY |
ASCII-armored GPG private key |
SIGNING_PASSWORD |
GPG private key passphrase |
Local publishing for smoke-testing:
./gradlew :prosemirror-compose:publishToMavenLocal :prosemirror-compose-coil3:publishToMavenLocalApache License 2.0 — see LICENSE.
This project is adapted from compose-rich-editor (Apache-2.0, © Mohamed Rejeb) and builds on the Kotlin port of ProseMirror, prosemirror-kotlin / com.atlassian.prosemirror:* (v1.1.20, Apache-2.0, © Atlassian Pty Ltd). The editor is re-packaged under com.github.wood.prosemirror.compose.