Press Enter once in a Markdown editor and the preview may keep the text in one paragraph. Move the same file to a chat app and every source line may become a visible line break.
That is usually not a parser bug. Three separate things are being confused:
- the file's line ending (
LF,CRLF, orCR), - the Markdown node (
softbreakor hard line break), and - the browser's final layout.
I tested the cases below on August 14, 2026 with fixed versions:
- commonmark.js 0.31.2
- markdown-it 15.0.0
- Marked 18.0.9
- CommonMark specification 0.31.2
One Enter is a soft break by default
Input:
alpha
beta
All three parsers produced one paragraph in their default configuration:
<p>alpha
beta</p>
CommonMark calls the line ending a soft break. A renderer may emit it as a newline character or a space. Under the browser's usual white-space: normal, that whitespace is collapsed, so the two words normally appear on one visual line unless the container wraps them.
This distinction matters: a newline in generated HTML source is not necessarily a visible line break.
Two spaces and a backslash create hard breaks
CommonMark supports two forms:
alpha
beta
alpha\
beta
Both produced a hard-break node and a <br> element in all three parsers:
<p>alpha<br>
beta</p>
I prefer the backslash form when a break is semantically important. It is visible during review, while trailing spaces are easy for an editor or formatter to delete.
LF, CRLF, and CR normalized to the same result
I rendered these inputs separately:
alpha\nbeta
alpha\r\nbeta
alpha\rbeta
All three parsers treated them as the same paragraph with a soft break by default. Converting a repository from CRLF to LF should therefore not change this Markdown meaning by itself.
Trailing-space cleanup is a different story. Removing the second space from a hard break changes the document structure.
The breaks option changes platform behavior
With breaks: true, markdown-it and Marked converted a normal paragraph newline to <br>:
| Configuration | Result for alpha\nbeta
|
|---|---|
| commonmark.js default | soft break |
| markdown-it default | soft break |
markdown-it breaks: true
|
<br> |
| Marked default | soft break |
Marked breaks: true
|
<br> |
That option explains many "works here, breaks there" reports. A chat renderer may intentionally preserve author-entered lines, while a documentation system follows CommonMark's paragraph behavior.
Treat the option as part of the platform's Markdown dialect. Recording only "we use Markdown" is not enough for a reproducible publishing pipeline.
Edge cases expose the real boundary
One trailing space is still soft
alpha
beta
It does not create a hard break. The parsers differed slightly in the exact HTML whitespace they retained, but not in the resulting Markdown structure.
Two spaces at the end of a block do not create <br>
alpha
next
CommonMark says a hard break separates inline content inside a block. It cannot occur at the end of the paragraph. commonmark.js and markdown-it removed the trailing spaces. Marked 18.0.9 retained them in the HTML string, but none of the three emitted <br>.
The page looks the same in a browser, yet byte-for-byte HTML snapshots differ. Decide whether your tests protect semantic structure or exact serialization.
Code spans use different whitespace rules
`alpha
beta`
All three produced:
<p><code>alpha beta</code></p>
The newline is normalized inside the code span. A backslash or two trailing spaces there do not become a hard break.
A practical testing strategy
For a multi-output documentation pipeline, test three layers:
- preserve the exact fixture, including visible line-ending and trailing-space notation;
- compare the parsed structure, especially soft-break and hard-break nodes;
- compare the target output and inspect it under the real CSS.
If the final target is fixed-layout output, I also check a representative file against the Markdown-to-PDF formatting workflow. That does not replace parser tests; it catches the separate class of layout differences introduced after parsing.
My working rule is simple: use blank lines for paragraphs, a visible backslash for intentional inline breaks, and automatic wrapping for prose. Then lock parser versions and options wherever the same source must render consistently.
Who should own line-break semantics in a shared document system: the author in the source, or the rendering environment for each destination?
Top comments (0)