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)
- ProseMirror document model — immutable
Nodetree as source of truth, withAnnotatedString+ span styles as the flat rendering projection - Formatting — bold, italic, underline, strikethrough, inline code, links, headings, text color/size; collapsed-selection staging (type, then format)
- Lists — bullet and ordered lists with wrap / lift / split, Enter/Tab handling, indent gutters, ordered numbers derived at render time
- Undo / redo via the ProseMirror history plugin
- HTML clipboard — copy and paste rich HTML through the ProseMirror
Slicepipeline - Markdown import / export —
setMarkdown,insertMarkdown,toMarkdown(JetBrains markdown/GFM parser → HTML → ProseMirror DOM parser) - Trigger system —
@mention-style triggers with token nodes inserted into the document - Inline images as atomic nodes, loaded via a pluggable
ImageLoader - Material 3 components —
ProseMirrorEditor(filled),OutlinedProseMirrorEditor, read-onlyProseMirrorText, expandable variants - Stability markers —
@ExperimentalProseMirrorApi/@InternalProseMirrorApion 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:
ProseMirrorStateholds thePMEditorState, the derived flatAnnotatedString, and theBasicTextFieldstate.PositionCoordinateMapmaps between ProseMirror structural positions and flat Compose text indices, in both directions.- Typing goes
TextField→ diff → ProseMirrorTransaction→ plugins/steps → rebuiltAnnotatedString; 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:
- Push to
main→ publishes0.1.0-SNAPSHOTto the Central Portal snapshot repository. - Push a
v*tag (for examplev0.1.0) → uploads0.1.0to 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.