Formulários que o HTML já valida sozinho (e onde isso não chega)
O browser já sabe validar um formulário. Faz isso sem uma linha de JavaScript, na língua de quem visita, com mensagens que um leitor de ecrã anuncia e com o foco a ir para o campo errado. É de graça, e metade da web reescreve isso à mão por cima.
Vale a pena saber exactamente até onde isso chega — e porque é que, mesmo chegando, nunca é suficiente.
1. Os atributos que fazem o trabalho
<label for="nome">Nome</label>
<input id="nome" name="nome" required maxlength="90" />
<input type="email" required />
<input type="number" min="1" max="10" step="1" />
<input pattern="[0-9]{9}" title="Nove dígitos" />Com isto o browser impede a submissão, põe o foco no primeiro campo inválido e mostra a mensagem. O type="email" traz ainda o teclado certo no telemóvel, que é uma melhoria real que nenhuma validação em JavaScript dá.
E o par label for / id não é validação, mas é o que faz a mensagem ter dono: sem ele, o browser aponta para um campo que a tecnologia de apoio não sabe nomear — o problema do artigo das cinco coisas de acessibilidade.
2. O pormenor que resolve o formulário vermelho ao carregar
Quem tenta estilizar isto tropeça sempre no mesmo:
/* Vermelho antes de a pessoa escrever uma letra */
input:invalid { border-color: red; }
/* Vermelho depois de ela mexer no campo */
input:user-invalid { border-color: red; }O :invalid é verdade desde o primeiro instante: um campo obrigatório e vazio é inválido. Pintá-lo logo é acusar alguém de não ter preenchido um formulário que ainda não começou.
O :user-invalid só passa a valer depois de a pessoa ter interagido com o campo, ou de ter tentado submeter. É o que quase todos querem, e é mais recente do que a maior parte dos artigos sobre o assunto.
3. Mensagens próprias, sem desligar nada
Quando a mensagem do browser não serve, não é preciso desligar a validação toda — há uma API:
campo.setCustomValidity(
campo.validity.tooShort ? 'Precisa de pelo menos 10 caracteres.' : ''
);A cadeia vazia é o que limpa o erro; esquecê-la deixa o campo inválido para sempre, e é o erro mais comum com esta API. O novalidate no formulário, que desliga tudo, fica para quem queira assumir a validação inteira — e aí assume também o foco, as mensagens e o anúncio.
4. E agora a parte onde isto não chega
Nada disto é segurança. Um formulário é o que quem o submete quiser. O required e o maxlength vivem num HTML que qualquer pessoa pode ignorar, e um pedido pode ser feito sem browser nenhum.
No formulário de comentários deste site a mesma regra está escrita duas vezes, de propósito:
// no HTML, para quem está a escrever
<input name="nome" required maxLength={90} />
// no servidor, onde a regra realmente existe
nome: z.string().trim().min(1).max(90)Não é repetição por desleixo: são duas coisas diferentes com o mesmo número. A primeira é conforto — avisa antes de a pessoa esperar por uma resposta. A segunda é a regra. Se discordarem, é a do servidor que ganha, e a do HTML passa a ser uma mentira simpática.
5. O campo escondido, que é o caso mais claro
O comentário leva o número do artigo num campo oculto. É cómodo e é indefensável sozinho — mudar aquele número é trivial. Por isso a ação confirma na base que o artigo existe antes de inserir o que seja:
const existe = await prisma.post.count({ where: { id: validado.data.postId } });Esta é a diferença entre validar e confiar. O caminho todo, do campo até à base, está no artigo dos formulários e Server Actions; aqui interessa só o princípio: tudo o que vem do formulário é uma sugestão.
6. O que não é validação mas vive no mesmo sítio
Há um campo a mais naquele formulário, fora do ecrã e fora da navegação por teclado. Quem vê a página nunca lhe toca; um robô que preencha tudo o que encontra no HTML preenche-o, e o comentário é deitado fora.
Com um detalhe que decide se funciona: não pode ser type="hidden", porque os robôs ignoram esses. Tem de parecer um campo a sério, com um nome que dê vontade de preencher.
E a segunda medida, que é a que apanha mais: o tempo de preenchimento. Menos de três segundos a escrever um comentário não é uma pessoa. O que vai para o servidor é a duração e não o instante — comparar relógios de máquinas diferentes dava recusas a quem tivesse o computador adiantado.
O resumo
Usar os atributos: são gratuitos, traduzidos e acessíveis. Usar :user-invalid e não :invalid. E escrever a regra outra vez no servidor, porque a do HTML não é uma regra — é um favor que se faz a quem está de boa-fé.