xiu kuang

Removing line breaks looks like a one-regex job. text.replace(/\n/g, '') and you're done. Then real text shows up, and it turns out line endings are a small historical mess.

There are three different characters that can end a line: LF, CRLF, and CR. Text copied from Word, a spreadsheet, or a GitHub issue uses whichever one its origin platform chose, and a single paste can contain several at once. Remove only \n and the \r characters are still there. Remove everything and paragraph boundaries collapse into a wall of text.

I built a line break remover as part of TextFixHub, a collection of free browser-based text utilities.

You can try it here:

TextFixHub Line Break Remover

Three modes, because the task is three different things

"Remove line breaks" means something different depending on what the text is for:

  • Replace with space — single breaks become spaces; blank lines stay as paragraph breaks. The default, and the safest.
  • Remove entirely — every break is deleted. Words will run together. Useful when you know the input has no fragile spots.
  • Remove with space — every break is deleted, but a space is added where each line ended, so words don't glue together.

The first version had two modes. The gap showed up fast: paste Hello\nWorld, pick "Remove entirely", get HelloWorld. That's not what anyone wants, so a third mode exists now.

The pipeline

  1. Strip the BOM if present.
  2. Normalize all line endings to LF.
  3. Apply the selected mode.
  4. Remove trailing whitespace.

The BOM

Some files edited on Windows start with a byte-order mark (U+FEFF). It's invisible, but it is the first character, and it quietly breaks naive logic. One line handles it:

if (text.charCodeAt(0) === 0xfeff) {
  text = text.slice(1);
}

Enter fullscreen mode Exit fullscreen mode

Line endings are a historical accident

Line ending Bytes Where it comes from
LF \n Unix, macOS, most web content
CRLF \r\n Windows
CR \r Classic Mac OS (rare now, but real)

Mixed endings appear in the same input more often than you'd expect. The fix is to normalize first, in the right order — CRLF has to be handled before CR, otherwise \r\n becomes \n\n and invents a phantom blank line:

const CRLF_RE = /\r\n/g;
const CR_RE = /\r/g;

text = text.replace(CRLF_RE, '\n').replace(CR_RE, '\n');

Enter fullscreen mode Exit fullscreen mode

Protecting paragraph breaks

"Replace with space" should keep paragraphs. Two or more consecutive newlines are a paragraph boundary, so they need to survive while single breaks turn into spaces.

The trick is a placeholder. Swap the paragraph break for a character the input can't contain — NUL (\0) — handle the single breaks, then put the paragraphs back:

const withParaBreaks = text.replace(DOUBLE_NEWLINE_RE, '\0');
const withSpaces = withParaBreaks.replace(NEWLINE_RE, ' ');
const result = withSpaces.replace(/\0/g, '\n');

Enter fullscreen mode Exit fullscreen mode

Three regex passes, no state machine, no index bookkeeping. The placeholder stays in my toolbox for any text transformation where some matches need to survive while others don't.

Why it stays in the browser

The input never leaves the device. No upload, no server-side API, no account. For a text tool, all of the work is local anyway, so the deployment stays static and the privacy story is easy to verify — the code that touches your text is right in the browser, not hidden behind an API.

Testing the edge cases

The behavior above stays correct only if the tests pin it down. The suite covers:

  • CRLF, LF, and CR endings individually
  • Mixed endings in one input (A\r\nB\nC\rA B C)
  • Paragraph preservation with 2 and 3 consecutive breaks
  • Empty input and single-word input
  • Trailing newlines
  • The BOM case
  • Large inputs: 100K+ characters processed in well under 500ms

What I learned

"Remove line breaks" is not one operation. It's at least three, and the difference between them matters to the user even when the user can't name it. Hello\nWorld becoming HelloWorld is the perfect example — paste a list, pick the wrong mode, get garbage, and no error message explains why.

The placeholder trick generalizes: pick a character the input can't contain, and protecting part of a match while transforming the rest stops being scary.

The finished tool is here:

TextFixHub Line Break Remover

The other TextFixHub tools are at https://www.textfixhub.com/.

If you spot any mistakes here, please point them out — I'd appreciate it. Thanks for reading.