Formulário de login bonito que funciona no teclado, no leitor de tela e no autofill
Monte um login com rótulo visível, autocomplete certo, botão de mostrar senha com nome, erro em texto perto dos campos e foco visível, sem perder o visual limpo.
O redesenho do login ficou elegante: um cartão branco, dois campos sem rótulo, só um ícone de envelope e um de cadeado, e um botão em gradiente. Na semana seguinte, o suporte recebe o triplo de chamados de "não consigo entrar". O gerenciador de senhas parou de preencher, a mensagem de erro aparece em vermelho claro sobre rosa e some em dois segundos, e quem navega por Tab não sabe em qual campo está. A resposta curta: um login legível tem rótulo visível em cada campo, atributos name e autocomplete corretos, erro em texto perto dos campos, foco visível e um botão "Entrar" que nunca fica desabilitado sem explicação. Dá para ter tudo isso e manter o cartão limpo.
O critério é simples: a pessoa consegue entrar de primeira, com teclado, com o gerenciador de senhas e com leitor de tela.
Por que o visual "minimalista" atrapalha o login
Placeholder no lugar do rótulo some assim que a pessoa começa a digitar. Se ela parar no meio, não sabe mais se o campo pede e-mail, usuário ou CPF. Além disso, o texto do placeholder costuma ter contraste baixo por padrão, e leitores de tela tratam placeholder de forma inconsistente como nome do campo.
O gerenciador de senhas, seja o do navegador ou um externo, decide o que preencher olhando para o formulário: tipo do campo, name, autocomplete e a presença de um campo de senha. Campos montados com div e JavaScript, nomes aleatórios ou autocomplete="off" confundem essa detecção. A pessoa que usa senha forte e única, que é o que você quer, fica sem conseguir colar ou preencher.
Erro só por cor falha para quem tem baixa visão ou daltonismo, e erro que desaparece sozinho falha para quem lê devagar. Já o contorno de foco removido com outline: none deixa quem usa teclado sem saber onde está.
Como montar o formulário
- Use um
formde verdade, commethod="post"e um botãotype="submit". - Dê a cada campo um
labelvisível ligado pelofor. - Use
autocomplete="username"no identificador eautocomplete="current-password"na senha. - Reserve um espaço para a mensagem de erro, acima dos campos, com
role="alert". - Coloque um botão "Mostrar senha" com texto, que troca o tipo do campo.
- Mantenha o contorno de foco, estilizado com
:focus-visible.
O exemplo é o login de um sistema de clínica, que identifica a pessoa pelo e-mail. Esta é a versão renderizada depois de uma tentativa com senha errada:
<form method="post" action="/entrar" class="login" novalidate>
<h2>Entrar na Clínica Bem-Estar</h2>
<p id="erro-login" class="login-erro" role="alert">
E-mail ou senha incorretos. Confira e tente de novo.
</p>
<label for="email">E-mail</label>
<input id="email" name="email" type="email" autocomplete="username"
value="ana@exemplo.com.br" required aria-describedby="erro-login">
<label for="senha">Senha</label>
<div class="senha-linha">
<input id="senha" name="senha" type="password" autocomplete="current-password"
required aria-describedby="erro-login">
<button type="button" id="mostrar-senha" aria-controls="senha">Mostrar senha</button>
</div>
<button type="submit">Entrar</button>
<p><a href="/esqueci-a-senha">Esqueci minha senha</a></p>
</form>
Repare que o e-mail volta preenchido e a senha volta vazia. A mensagem de erro fica no topo, ligada aos campos por aria-describedby, então o leitor de tela anuncia a mensagem ao entrar em qualquer um deles. Sem erro, o servidor simplesmente não renderiza esse parágrafo nem o aria-describedby.
O botão de mostrar senha
const campo = document.getElementById('senha');
const botao = document.getElementById('mostrar-senha');
botao.addEventListener('click', () => {
const mostrando = campo.type === 'text';
campo.type = mostrando ? 'password' : 'text';
botao.textContent = mostrando ? 'Mostrar senha' : 'Ocultar senha';
campo.focus();
});
O botão tem type="button" para não enviar o formulário, e o texto muda conforme o estado, então quem usa leitor de tela ouve a ação disponível. Se o visual pede um ícone de olho, mantenha o texto dentro do botão, visualmente escondido, e marque o ícone como decorativo.
O CSS que mantém o cartão limpo e legível
.login {
max-width: 24em;
margin: 3em auto;
padding: 2em;
background: #fff;
border-radius: 8px;
}
.login label {
display: block;
margin-top: 1em;
font-weight: 600;
}
.login input {
width: 100%;
font-size: 1rem;
padding: 0.6em;
border: 1px solid #6b7280;
border-radius: 4px;
}
.login :focus-visible {
outline: 3px solid #1d4ed8;
outline-offset: 2px;
}
.login-erro {
padding: 0.75em;
border-left: 4px solid #b91c1c;
background: #fef2f2;
color: #7f1d1d;
}
A borda cinza-escura dá aos campos contraste suficiente contra o fundo branco; as diretrizes de acessibilidade pedem 3:1 para contorno de componentes e 4,5:1 para texto comum. A fonte de 1rem nos campos também evita que o Safari do iPhone dê zoom automático ao focar, o que acontece com campos de fonte menor que 16 pixels. O erro tem texto, barra lateral e fundo, e não depende só do vermelho.
O que a mensagem de erro não deve dizer
A mensagem é uma só para e-mail inexistente e senha errada. Dizer "este e-mail não está cadastrado" entrega a quem tenta adivinhar contas uma forma de descobrir quem é cliente da clínica, como explica o post sobre erro de login genérico. E a senha, do lado do servidor, é conferida contra um hash, nunca guardada em texto, como no guia de login com senha protegida.
Também não desabilite o botão "Entrar" até os campos estarem "válidos". Botão desabilitado não recebe foco, não explica o motivo e costuma travar quando o gerenciador de senhas preenche os campos sem disparar os eventos que o script espera. Deixe enviar e mostre o erro.
Erros comuns
- Placeholder no lugar do rótulo. O nome do campo some ao digitar e o leitor de tela pode não anunciar o que o campo pede.
autocomplete="off"ou nomes aleatórios nos campos. O gerenciador de senhas não preenche, e a pessoa volta a usar senha fraca para conseguir lembrar.- Bloquear colar no campo de senha. Quem usa gerenciador de senhas fica sem conseguir entrar.
- Erro só em cor ou que desaparece sozinho. Parte das pessoas não percebe, ou não tem tempo de ler, e tenta de novo do mesmo jeito.
outline: nonesem substituto. Quem navega por Tab não sabe em qual campo está digitando.- Ícone de olho sem nome. O leitor de tela anuncia "botão" e a pessoa não sabe que pode ver a senha.
Checklist
- Cada campo tem
labelvisível ligado pelofor. - Os campos têm
autocomplete="username"eautocomplete="current-password". - O gerenciador de senhas do navegador preenche e salva a senha.
- Com só o teclado, você chega aos campos, ao botão de mostrar senha e ao "Entrar", vendo o foco em cada um.
- Uma senha errada mostra uma mensagem em texto, perto dos campos, que não some sozinha.
- A mensagem é a mesma para e-mail inexistente e senha errada.
- Colar no campo de senha funciona.
Para os outros formulários do sistema, o guia de formulário que não cansa cobre rótulos, erros e botões em geral. Faça agora o teste completo: entre no seu login com uma senha errada usando só o Tab e o Enter. Você entendeu o que falhou sem tocar no mouse?
Referência: http://tympanus.net/codrops/2012/10/16/custom-login-form-styling/