Quebra de linha que some no HTML: \n, nl2br, pre-line e o escape na ordem certa
Entenda por que o \n do banco vira uma linha só na página, mostre parágrafos com nl2br ou white-space: pre-line, escape antes de converter e saiba quando o texto deve ser texto puro.
O cliente escreveu o pedido de orçamento em três parágrafos: o que precisa, o prazo e o endereço da obra. No banco, o texto está lá com as quebras. No painel administrativo, aparece num bloco único, com o endereço grudado no prazo. Você tenta echo $texto . '\n' e nada muda. A resposta curta: o HTML trata quebra de linha como um espaço qualquer. Para a quebra aparecer, você converte o texto em tags (<br> ou <p>) depois de escapar o conteúdo, ou usa o CSS white-space: pre-line no elemento que mostra o texto. Em PHP, a forma curta é nl2br(htmlspecialchars($texto, ENT_QUOTES, 'UTF-8')), nessa ordem.
A ordem não é detalhe. Converter primeiro e escapar depois mostra <br> escrito na tela. Não escapar abre a porta para o usuário injetar HTML e script no seu painel.
Por que o navegador ignora o \n
No HTML, espaços, tabulações e quebras de linha seguidas são colapsados num único espaço. Isso é de propósito: permite indentar o código-fonte da página sem afetar o que aparece. O efeito colateral é que o texto vindo do banco, com seus caracteres de quebra, é desenhado como uma linha corrida. A quebra existe no conteúdo; o navegador só não a desenha.
Em outros destinos, a mesma quebra funciona. Um arquivo .txt, um CSV, o terminal e um e-mail em texto puro respeitam o caractere de quebra. É por isso que o mesmo texto parece certo no e-mail de confirmação e errado no painel.
Há ainda a questão de qual caractere é a quebra. Linux usa \n, o Windows tradicionalmente usa \r\n, e o navegador, ao enviar um textarea, normaliza as quebras para \r\n. Código que procura só \n ou só \r\n acaba tratando metade dos casos.
A pegadinha das aspas no PHP
Em PHP, '\n' entre aspas simples é literalmente uma barra e a letra n. Só "\n" entre aspas duplas vira o caractere de quebra. Muita gente "testa a quebra" com aspas simples, vê \n escrito na tela e conclui que o problema é outro.
echo 'Linha 1\nLinha 2'; // imprime: Linha 1\nLinha 2
echo "Linha 1\nLinha 2"; // imprime duas linhas no terminal,
// mas uma linha só no navegador
Como mostrar as quebras do jeito certo
- Defina o que o campo guarda: texto puro, como o orçamento, ou HTML, como o corpo de um artigo vindo de editor rico.
- Guarde o texto puro como o usuário digitou, sem converter nada na entrada.
- Na saída para HTML, escape primeiro com
htmlspecialchars. - Depois converta as quebras:
nl2brpara quebras simples, ou divida em parágrafos. - Para e-mail em texto puro, CSV ou arquivo, use o texto original, sem tags.
Opção 1: nl2br, a mais simples
<?php
$texto = $pedido['descricao'];
?>
<div class="descricao">
<?= nl2br(htmlspecialchars($texto, ENT_QUOTES, 'UTF-8'), false) ?>
</div>
O nl2br insere um <br> antes de cada quebra e reconhece as variações \r\n, \n e \r. O segundo argumento false gera <br> em vez de <br />, que é a forma comum em HTML5; as duas funcionam. Se o cliente digitar <script> na descrição, o htmlspecialchars já transformou os sinais em entidades antes, e o texto aparece como texto.
Opção 2: parágrafos de verdade
Quando o texto tem parágrafos separados por linha em branco, transformar cada bloco em <p> dá espaçamento melhor do que uma pilha de <br>.
<?php
function textoParaHtml(string $texto): string
{
$blocos = preg_split('/\R\s*\R/', trim($texto));
$html = '';
foreach ($blocos as $bloco) {
$seguro = htmlspecialchars($bloco, ENT_QUOTES, 'UTF-8');
$html .= '<p>' . nl2br($seguro, false) . "</p>\n";
}
return $html;
}
Na expressão regular, \R casa qualquer sequência de quebra, então a função funciona com texto vindo do Windows ou do Linux. Duas quebras com espaços no meio separam parágrafos; uma quebra simples dentro do bloco vira <br>. O escape continua acontecendo antes de qualquer tag ser adicionada.
Opção 3: deixar o CSS desenhar as quebras
.descricao {
white-space: pre-line;
}
Com pre-line, o navegador respeita as quebras do texto, continua juntando espaços repetidos e quebra linhas longas no fim do espaço disponível. Você imprime só o texto escapado, sem nl2br. É útil quando o texto chega por JavaScript: atribua com textContent, que não interpreta HTML, e o CSS cuida das quebras. Evite white-space: pre ou a tag pre para texto comum: elas não quebram linhas longas, e no celular o texto ganha rolagem horizontal.
// JavaScript: seguro e com quebras, graças ao pre-line
document.querySelector('.descricao').textContent = pedido.descricao;
// ERRADO: troca quebras por <br> e injeta HTML do usuário na página
// el.innerHTML = pedido.descricao.replace(/\n/g, '<br>');
Uma convenção por campo
O problema mais caro não é a quebra sumir, é a mistura. Se parte dos registros foi salva já com <br> e parte com quebras puras, a tela ora mostra a tag escrita, ora come a quebra, e ninguém sabe qual função aplicar. Decida por campo: descrição de orçamento é texto puro e passa por escape e nl2br na saída; corpo de artigo é HTML vindo do editor e passa por um sanitizador que permite só as tags aceitas. Não use a mesma função de saída para os dois. O post sobre editor rico no lugar da textarea trata do lado HTML, e o de entidade HTML explica por que o texto não deve ser guardado já escapado.
No e-mail, a mesma regra vale ao contrário. A versão em texto puro usa a descrição original. A versão HTML usa a saída escapada e convertida. Montar o texto puro a partir do HTML, ou o contrário, costuma deixar & e <br> perdidos na mensagem. O guia de formulário de contato que chega mostra o envio completo.
Erros comuns
- Testar com
'\n'entre aspas simples no PHP. Aparece barra e n na tela, e o diagnóstico vai para o lugar errado. - Converter quebras antes de escapar. O
<br>vira texto visível na página, e alguém "conserta" tirando o escape. - Não escapar o texto do usuário. Qualquer HTML digitado no formulário roda no painel de quem lê.
- Salvar no banco já com
<br>. O e-mail em texto puro, o CSV e a API passam a receber tags no meio do conteúdo. - Usar
prepara preservar as linhas. Linhas longas não quebram e o celular ganha rolagem horizontal. - Tratar só
\nou só\r\n. Texto vindo de outro sistema operacional ou do formulário fica com quebras pela metade.
Checklist
- Cada campo de texto tem convenção definida: texto puro ou HTML.
- Texto puro é guardado como foi digitado, sem tags.
- A saída em HTML escapa antes de converter quebras.
- Um texto com
<b>teste</b>aparece literalmente na tela, sem negrito. - Parágrafos digitados no formulário aparecem separados na página.
- O e-mail em texto puro usa o texto original, sem entidades nem tags.
- Linhas longas quebram no celular sem rolagem horizontal.
Abra o formulário do seu sistema, digite três parágrafos e um <b>teste</b> no meio, envie e veja a tela de quem recebe. Os parágrafos estão separados, e a tag apareceu como texto ou virou negrito?
Referência: https://www.vivaolinux.com.br/topico/PHP/Pular-Linha-PHP