Markdown
Markdown import and export for the Compose Text Editor: install it on a state to use the rich text editor as a markdown editor. The document stays rich text; markdown is the text it is read from and written to.
Try it live: open the Markdown demo on Wasm »
implementation("com.darkrockstudios:composetexteditor-markdown:2.0.0")Recipe
Wrap a state with withMarkdown to import and export GitHub-flavored Markdown:
val state = rememberTextEditorState()
val markdown = remember(state) { state.withMarkdown() }
// Import handles both inline (**bold**, *italic*, `code`) and block elements
// (headings, lists, blockquotes, code fences, horizontal rules):
LaunchedEffect(markdown) {
markdown.importMarkdown(
"""
# Title
Some **bold** and *italic* text.
- one
- two
""".trimIndent()
)
}
TextEditor(state = state)
// Export the current document back to a Markdown string:
val source: String = markdown.exportAsMarkdown()The styles the document is rendered and recognised with are the editor's own, TextEditorState.richTextStyles (a RichTextStyles); assign them before importing, and change them through the state, which rebakes the headings. MarkdownConfiguration holds the syntax choices CommonMark leaves open: how a highlight is written (==text== or <mark>) and whether a line is a paragraph, with a blank line after it, or a source line.
The block toggles and queries (toggleBulletList, toggleHeader, headerLevel, setLink and the rest) are on the state, in com.darkrockstudios.texteditor.state, so a toolbar needs no markdown to drive them.
To format by typing markdown, install MarkdownShortcuts: - , 1. , # and > at a line's start make the block, **bold** and the other inline syntax the style, and one undo gives back what was typed. It needs no withMarkdown; any rich text editor can take it:
state.editBehaviors.add(0, MarkdownShortcuts())Images are only reconstructed on import when an ImageProvider is supplied (state.withMarkdown(imageProvider = myProvider)); without one every  stays literal text.
To render only inline Markdown into an AnnotatedString (no block handling), use String.toAnnotatedStringFromMarkdown; prefer importMarkdown whenever the source contains block elements. AnnotatedString.toMarkdown is the inverse.
With spell check
A spell-checked editor is a TextEditorState underneath:
val state = rememberSpellCheckState(spellChecker = spellChecker)
val markdown = remember(state) { state.textState.withMarkdown() }
LaunchedEffect(markdown) { markdown.importMarkdown(source) }Packages
Markdown import and export: withMarkdown, MarkdownExtension, the syntax choices in MarkdownConfiguration, and the AnnotatedString to Markdown converters.