Programação Por Equipe DevBras

Capa do artigo: Formulário de login bonito que funciona no teclado, no leitor de tela e no autofill

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

  1. Use um form de verdade, com method="post" e um botão type="submit".
  2. Dê a cada campo um label visível ligado pelo for.
  3. Use autocomplete="username" no identificador e autocomplete="current-password" na senha.
  4. Reserve um espaço para a mensagem de erro, acima dos campos, com role="alert".
  5. Coloque um botão "Mostrar senha" com texto, que troca o tipo do campo.
  6. 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: none sem 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 label visível ligado pelo for.
  • Os campos têm autocomplete="username" e autocomplete="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?

Avalie este artigo

Referência: http://tympanus.net/codrops/2012/10/16/custom-login-form-styling/