Inputs
Input coleta dado. Parece simples, mas tem muito contexto implícito que o usuário precisa para preencher corretamente. O trabalho do designer e do dev é deixar esse contexto explícito.
Label não é opcional
Placeholder não é label. Ele desaparece quando o usuário começa a digitar — e some completamente para quem usa screen reader antes de interagir com o campo.
A conexão semântica entre label e input é feita com id + htmlFor. Sem isso, um leitor de tela anuncia apenas "campo de texto" — sem contexto nenhum do que preencher.
/* Errado — sem label */
<input type="text" placeholder="Nome completo" />
/* Certo — label conectado */
<label for="nome">Nome completo</label>
<input id="nome" type="text" placeholder="Ex: Ana Silva" />Com a conexão correta, o screen reader anuncia "Nome completo, campo de texto". O usuário sabe exatamente o que preencher.
Estados e feedback
Um input tem mais estados do que parece:
- default: borda sutil, placeholder em cinza claro
- focus: borda destacada — nunca remova o outline sem substituir por algo equivalente
- filled: dado preenchido, borda volta ao normal
- error: borda vermelha + mensagem de erro embaixo
- disabled: opacidade reduzida,
cursor-not-allowed, não editável
Tente preencher o campo de email abaixo com um valor inválido e sair do campo:
Validação e erro
Regras de quando e como validar:
- Valide no blur (quando o usuário sai do campo), não enquanto digita — validar durante a digitação é agressivo
- Nunca confie só em cor para indicar erro — quem tem daltonismo não vai ver. Use ícone ou texto junto
- A mensagem de erro precisa estar conectada ao input via
aria-describedby
/* HTML */
<label for="email">Email</label>
<input type="email" id="email" placeholder="exemplo@email.com" />
<span class="error" id="email-erro" role="alert"></span>/* CSS */
input {
display: block;
width: 100%;
padding: 0.5rem;
border: 1px solid var(--border);
border-radius: 0.375rem;
}
input:focus {
outline: none;
border-color: var(--primary);
}
input[aria-invalid="true"] {
border-color: var(--error);
}
.error {
color: var(--error);
font-size: 0.875rem;
margin-top: 0.25rem;
}/* JavaScript */
const input = document.getElementById('email');
const erro = document.getElementById('email-erro');
input.addEventListener('blur', () => {
const email = input.value;
const valido = /@/.test(email);
input.setAttribute('aria-invalid', !valido);
erro.textContent = valido ? '' : 'Email inválido';
});O aria-describedby conecta a mensagem de erro ao campo. O role="alert" na mensagem faz com que o screen reader anuncie o erro automaticamente quando ele aparece.
A decisão
Neste guia, todo input tem label visível e acima do campo. Mensagem de erro aparece abaixo, em vermelho, com aria-describedby conectando os dois. O estado de focus usa var(--border-highlight) sem outline padrão do browser — mas só porque substituímos por algo equivalente.