Editor-style text
The display component normalizes whitespace the way CommonMark does: single newlines collapse into spaces, and runs of blank lines collapse into a single paragraph break. That is the right default for prose, but it means the rendered output does not match the raw line layout the user typed.
The editor is a WYSIWYG surface: the user sees formatted text as they type, and each Enter and blank line they add is captured in the Markdown it produces. The round trip is exact because the editor's serializer is line-preserving - it splits on \n, rejoins on \n, and never lets an inline delimiter cross a newline - so one Enter press is exactly one \n in the output. All that is left is to tell the renderer not to normalize it away.
Two parser flags do that, and a style tweak makes the result look right:
- Soft breaks to line breaks. Pressing Enter once in the editor produces a single newline (a soft break), which normally collapses to a space. The
hardSoftBreaksflag renders each soft break as a visible line break instead. Default:false. - Blank lines to empty lines. Pressing Enter several times produces a run of blank lines, which CommonMark collapses to a single break. The
preserveBlankLinesflag keeps every blank line - each renders as one empty line, so the output keeps the exact number of blank lines that were typed. Default:false. - Zero the paragraph bottom margin. Paragraph margins stack on top of blank-line spacing.
paragraph.marginTopis already0; it ismarginBottom, which defaults to16, that you need to zero so the blank lines alone drive the vertical rhythm.
- React Native
- iOS
- Android
<EnrichedMarkdownText
markdown={markdownFromEditor}
md4cFlags={{ hardSoftBreaks: true, preserveBlankLines: true }}
markdownStyle={{ paragraph: { marginTop: 0, marginBottom: 0 } }}
/>
EnrichedMarkdownText(
markdownFromEditor,
options: MarkdownParsingOptions(hardSoftBreaks: true, preserveBlankLines: true)
)
.markdownTheme {
Paragraph()
.marginBottom(0)
}
The flags are fields of options; the margin is set on Paragraph(), whose top margin is already unset.
EnrichedMarkdownText(
markdown = markdownFromEditor,
options = MarkdownOptions(hardSoftBreaks = true, preserveBlankLines = true),
style = MarkdownTheme.style.merge {
paragraph {
marginTop = 0.dp
marginBottom = 0.dp
}
},
)
The flags are named arguments of MarkdownOptions - they are not its first two parameters, so pass them by name.
hardSoftBreaks applies to every soft break, not just the ones the editor produced. Prose that was hand-wrapped at 80 columns somewhere else will stop reflowing and keep its original line endings.
Putting it together
Combine both flags with zeroed paragraph margins to reproduce editor content line for line:
With this configuration, every newline and every blank line the user typed in the editor is rendered verbatim - the round trip preserves the exact line layout, which is what chat-style and note-taking apps expect. In the React Native package both flags work on iOS, Android and web; the standalone iOS and Android SDKs take the same two flags, as shown in the tabs above.
Both flags belong to the read-only renderer; they describe how it parses an existing Markdown string. EnrichedMarkdownTextInput has no md4cFlags prop at all - there is nothing to configure on the editing side, because its serializer already preserves the lines.
Reference
md4cFlags- the full flag reference, with a live playground per flag.- Line breaks and Blank lines - the underlying newline and blank-line behavior.
markdownStyle.paragraph- the margin defaults you are overriding.- iOS -
options(MarkdownParsingOptions), the line-break and blank-line options, Parser extensions, andParagraph()margins. - Android -
options(MarkdownOptions), the line-break and blank-line options, Parser extensions, andparagraphmargins.