Compose has been missing a Rich Text Editor since its inception. I've taken a crack at this previously, as have others.
However, they have all suffered from fundamental limitations in BasicTextField, the foundation of
all text entry in Compose.
This project is an attempt to re-implement text entry from scratch to finally have a solution to the various problems.
I've been trying to implement a spell checking text
field in Compose, and keep running up against
the limitations of BasicTextField. Pretty much out of options, I decided to see what it might take
to replace BasicTextField with something that solved all of my needs.
And now, it's working, and at this point, working pretty well.
- ✅ 100% Compose Multiplatform
- ✅ Behaves like each platform's own text fields: keys, mouse, touch, IME, clipboard
- ✅ Fast on long documents
- ✅ Rich text with custom spans
- ✅ Block structure: headings, nested lists, task lists, quotes, code fences, tables, rules, images, links
- ✅ Spell check, find & replace, and diagnostics from your own checker
- ✅ Screen readers
- ✅ HTML import, export and clipboard
- ☑️ Markdown, as an addon
- CommonMark: 532 of the spec's 652 examples, and every other one not planned (spec support)
- GitHub Flavored Markdown: tables, task lists and strikethrough; bare links are not planned
- Underline (
<u>), highlight (==text==or<mark>), colour and size (<span style>) - Opt-in shortcuts that format as you type (
#,-,**bold**)
You can Give it a try here, or browse the API reference & recipes.
- Only what is visible is drawn: a 200,000 character document opens in about 7 ms on desktop
- Exposed scroll state, so we can render scroll bars (BTF1 can't do this)
- Doesn't copy and return full contents on each edit, so better for longer form text (BTF2 also works this way, but doesn't support AnnotatedString for rich content)
- Custom Rich Span drawing (this lets us render the traditional Spell Check red squiggle)
- Emits edit events: collect a Flow and know exactly what changed, so spell check only re-checks the word that was edited (BTF2 now finally offers this!)
- Undo and redo, one step per user action
- Decoration layers: highlights such as find matches or syntax colours that stay out of undo and export
- Paragraph formatting: spacing, alignment, indents and line height
- Read-only, single line, max length, input filters and no-wrap modes
- State that survives process death, or lives in your view model
- Opt-in smart punctuation and auto-linking (docs/design/behaviors.md)
Green is supported and yellow is not supported yet. Grey is not planned:
- Raw HTML. Rendering freeform HTML would mean building an HTML rendering engine, so HTML beyond the style tags above is kept as text.
- What the editor's line model cannot hold. A line is a paragraph with one stack of blocks (a quote around a list item around its text), so blocks nested any other way, such as a code block inside a list item, lose their nesting but keep their content. The editor also keeps every empty line, which markdown can only write as a blank line a renderer drops.
CommonMark: 532 of the spec's 652 examples
Not planned: raw HTML (71), blocks inside list items or block quotes (26), list items holding more than one paragraph (10), blank lines kept as empty lines (10), and images inside text or links (3).
GitHub Flavored Markdown extensions: 12 of 24 examples
- Tables, task lists and strikethrough are supported
- Not planned: bare links (11), since a URL typed as text would read back as a link (the editor's opt-in auto-linking makes them as you type), and filtering raw HTML tags (1), since raw HTML is kept as text
| Platform | Status |
|---|---|
| Desktop (JVM) | Supported |
| Android | Supported |
| iOS | Beta: typing, autocorrect, CJK composition, the edit menu, loupe and touch handles |
| WASM | Beta: typing, IME composition, the rich clipboard and drag and drop |
Text Editor:
implementation("com.darkrockstudios:composetexteditor:3.1.0")
Markdown addon, to use the editor as a markdown editor (state.withMarkdown()):
implementation("com.darkrockstudios:composetexteditor-markdown:3.1.0")
Spell Checking addon:
implementation("com.darkrockstudios:composetexteditor-spellcheck:3.1.0")
Find & Replace addon:
implementation("com.darkrockstudios:composetexteditor-find:3.1.0")
Or take the BOM and leave the versions off the modules, so they always match:
implementation(platform("com.darkrockstudios:composetexteditor-bom:3.1.0")
implementation("com.darkrockstudios:composetexteditor")
implementation("com.darkrockstudios:composetexteditor-markdown")
implementation("com.darkrockstudios:composetexteditor-spellcheck")
implementation("com.darkrockstudios:composetexteditor-find")Upgrading from 2.x (markdown as its own module, decoration layers, shortcuts matched on the keyboard layout): see docs/MIGRATION.md. How the editor is built is in docs/ARCHITECTURE.md, and how it is tested in docs/TESTING.md.
On iOS, host the Compose view with .ignoresSafeArea(.keyboard) in SwiftUI, as the
Compose Multiplatform template does. The editor keeps its caret above the keyboard itself;
letting SwiftUI shrink the view as well moves the content twice.
On Android, declare android:windowSoftInputMode="adjustResize" on the activity, as
Compose apps should; otherwise Android also pans the whole window to the caret. The
editor keeps its caret in view whether the host pads it by the keyboard's inset
(imePadding) or lets the keyboard cover it. TextEditorState.keyboardSettings asks the
keyboard for its capitalisation, autocorrect, layout and action key; an editor for code
would turn capitals and autocorrect off. Android, iOS and the web honour it; the web
ignores autocorrect.
I don't know. Maybe. It might not be a permanent solution.
BasicTextField2's new state based approach and gap-buffer has reached maturity and includes a lot of what I would need
to replace this, but still cannot handle rich text rendering.
So until that is solved, this is the only solution that I am aware of that ticks every box.
