Div com contenteditable no lugar do campo: o texto não vai no formulário
Entenda por que uma div editável não envia valor, não tem rótulo e aceita HTML colado, e como trocar por textarea ou, se precisar dela, sincronizar, rotular e tratar o texto no servidor.
O campo "Observações do pedido" da loja era uma div com contenteditable, porque o designer queria que ela crescesse com o texto. O placeholder era outra div por cima, escondida com script quando a pessoa digitava. Funcionou na demonstração. Em produção, os pedidos chegavam sem observação nenhuma, e o cliente que escreveu "não tocar a campainha, bebê dormindo" ouviu a campainha. Uma div editável não é campo de formulário: o texto dela não vai no envio.
A resposta curta: se é texto simples, de uma linha ou um parágrafo, use input ou textarea com label. Se você realmente precisa da área editável, dê a ela papel de caixa de texto e um rótulo, copie o conteúdo para um campo escondido no envio, prefira o modo de texto puro e trate o que chega no servidor como texto não confiável.
Por que o texto da div não chega ao servidor
Quando um formulário é enviado, o navegador monta o corpo com os valores dos controles de formulário que têm name: input, textarea, select e alguns outros. Uma div, editável ou não, não é um controle. Ela não tem name, não tem value e não participa da coleta. O texto está visível na tela e fica fora do POST.
O contenteditable transforma a div num pequeno editor de documento, e com isso você herda tudo que um campo fazia sozinho e agora precisa ser feito à mão:
- Rótulo: o
labelcomforsó se associa a controles. Sem ele, o leitor de tela anuncia "editável" sem dizer o quê. - Placeholder: uma div irmã por cima não é anunciada como dica do campo e pode até roubar o clique.
- Validação:
requiredemaxlengthnão se aplicam a div. - Colar: no modo padrão, colar de um editor de texto traz HTML, com negrito, fontes e às vezes imagens.
- Preenchimento automático e restauração: o navegador não guarda nem restaura o conteúdo se a página recarregar.
A correção mais simples: textarea com rótulo
Na maioria dos casos, o motivo da div era visual: crescer com o texto, não ter cara de caixa cinza. Os dois se resolvem com textarea e CSS:
<label for="observacao">Observações do pedido</label>
<textarea id="observacao" name="observacao" rows="3" maxlength="500"
aria-describedby="observacao-dica"></textarea>
<p id="observacao-dica">Ex.: entregar após as 14h, deixar na portaria.</p>
textarea {
width: 100%;
border: 1px solid #4A5866;
border-radius: 8px;
padding: 0.75rem;
font: inherit;
resize: vertical;
field-sizing: content;
min-height: 4.5rem;
}
A propriedade field-sizing: content faz o campo crescer com o texto nos navegadores que já a suportam; nos outros, ele mantém a altura de três linhas e a pessoa pode redimensionar. Em nenhum caso o texto se perde. A dica fica visível fora do campo, porque o placeholder some ao digitar e leva a instrução junto, como mostra o guia de marca d'água no campo.
Quando a div editável é necessária, como fazer direito
Há casos legítimos: menções com @, destaque de trechos, um editor de texto rico. Se for o seu caso, siga estes passos:
- Prefira o modo de texto puro. O valor
contenteditable="plaintext-only"faz a colagem chegar sem formatação. Ele é recente em parte dos navegadores; confira o suporte do seu público e, sem ele, trate a colagem no eventopaste. - Dê papel e nome. Use
role="textbox",aria-multiline="true"earia-labelledbyapontando para o texto do rótulo. - Faça o placeholder com CSS, não com outra div. Use o atributo
aria-placeholderpara o leitor de tela e um pseudoelemento para quem vê. - Copie o conteúdo para um campo escondido no envio. O
input type="hidden"comnameé o que vai no POST. - Valide e trate no servidor. Tamanho máximo, obrigatoriedade e escape na hora de exibir.
<form method="post" action="/pedidos/1234/observacao" id="form-obs">
<span id="obs-rotulo">Observações do pedido</span>
<div id="obs-editor" contenteditable="plaintext-only"
role="textbox" aria-multiline="true"
aria-labelledby="obs-rotulo"
aria-placeholder="Ex.: entregar após as 14h"
data-placeholder="Ex.: entregar após as 14h"></div>
<input type="hidden" name="observacao" id="obs-valor">
<button type="submit">Salvar</button>
</form>
<script>
document.getElementById('form-obs').addEventListener('submit', function () {
const texto = document.getElementById('obs-editor').innerText.trim();
document.getElementById('obs-valor').value = texto;
});
</script>
#obs-editor:empty::before {
content: attr(data-placeholder);
color: #4A5866;
}
O innerText devolve o texto com as quebras de linha que a pessoa vê, sem as tags. Um detalhe que pega muita gente: depois de digitar e apagar tudo, alguns navegadores deixam um br dentro da div, e o seletor :empty deixa de casar. O placeholder não volta. Se isso acontecer no seu caso, limpe o conteúdo no evento input quando o texto ficar vazio.
Para componentes reutilizáveis, existe ainda a via dos elementos personalizados associados a formulário, com ElementInternals e setFormValue, que fazem o componente participar do envio como um campo de verdade. É mais trabalho, e vale quando o mesmo editor aparece em muitos formulários.
No servidor, o texto é entrada do usuário
O campo escondido pode ser alterado por qualquer pessoa, como qualquer outro campo. Confira tamanho e obrigatoriedade no servidor e, ao exibir a observação no painel da loja, escape o texto. Nunca grave o innerHTML da div e o exiba de volta como HTML: é o caminho direto para injeção de script no painel de quem atende o pedido. Se o conteúdo precisa mesmo ser rico, a sanitização por lista de tags permitidas no servidor está no guia de editor rico no lugar da textarea.
Erros comuns
- Usar div editável para texto simples. O valor não vai no POST e o pedido chega sem a informação que a pessoa escreveu.
- Placeholder feito com outra div. O leitor de tela não o anuncia, e ele pode capturar o clique que deveria ir para a área editável.
- Rótulo solto sem associação. Visualmente há um título; para tecnologia assistiva, a área não tem nome.
- Enviar e exibir o
innerHTML. HTML colado e script injetado aparecem no painel interno. - Confiar só na sincronização por script. Se o JavaScript falhar, o campo escondido vai vazio; o servidor precisa tratar ausência sem gravar lixo.
- Esquecer o limite de tamanho. Sem
maxlengthpossível na div, só o servidor segura um texto de um megabyte.
Checklist
- Campos de texto simples usam
inputoutextareacomlabel. - Toda área editável tem
role="textbox"e nome acessível. - O placeholder da área editável é feito com CSS e
aria-placeholder. - O conteúdo chega ao servidor por um campo com
name. - A colagem não traz formatação quando o campo é de texto puro.
- O servidor valida tamanho e obrigatoriedade.
- O texto é escapado ao ser exibido em qualquer tela.
Abra as ferramentas do navegador, envie o seu formulário com texto na área editável e olhe a aba de rede: o texto que você escreveu aparece no corpo da requisição?
Referência: http://stackoverflow.com/questions/16941360/how-to-create-a-placeholder-for-div-that-act-like-textfield