Programação Por Equipe DevBras

Capa do artigo: Foco no campo com erro: o formulário Blazor que leva o cursor aonde precisa

Foco no campo com erro: o formulário Blazor que leva o cursor aonde precisa

Coloque o foco no primeiro campo ao abrir o formulário, leve-o ao primeiro campo inválido depois do envio e saiba quando não mexer no foco, com exemplo em Blazor.

O cadastro de clientes tem doze campos. A atendente preenche, aperta Enter, e nada parece acontecer. A mensagem "CPF inválido" apareceu em vermelho, lá no meio da página, fora da tela. O cursor continua no botão Salvar. Quem usa leitor de tela nem sabe que houve erro. A resposta curta: depois de um envio com erro, mova o foco para o primeiro campo inválido; ao abrir uma tela cuja tarefa principal é o formulário, coloque o foco no primeiro campo; e fora disso, não mexa no foco sem que a pessoa tenha pedido.

O exemplo é esse cadastro, num app Blazor com EditForm e validação por DataAnnotations. A mesma lógica vale para HTML puro, e há uma versão em JavaScript no fim. Para o foco que entra e sai de modais, veja modal que devolve o foco; para o anel de foco que some no tema escuro, foco visível no escuro.

Por que o foco importa mais do que a mensagem

O foco é o lugar onde a próxima tecla cai. Quem usa mouse compensa um foco mal posicionado clicando no campo certo. Quem usa teclado precisa apertar Tab várias vezes até achar o campo errado, sem saber qual é. Quem usa leitor de tela ouve o que está em foco; se o foco ficou no botão, ouve "Salvar, botão" e mais nada.

Por isso a mensagem de erro, sozinha, não basta. Ela precisa estar ligada ao campo (com aria-describedby ou dentro do rótulo) e o foco precisa ir até lá. Quando o foco chega a um campo com mensagem associada, o leitor anuncia o rótulo, o valor e o erro de uma vez.

Quando mover o foco e quando não mover

  • Mover: ao abrir uma página que é só o formulário (login, cadastro); depois de um envio que falhou na validação; ao abrir um diálogo; ao fechar um diálogo, de volta a quem o abriu.
  • Não mover: enquanto a pessoa digita, porque uma busca em segundo plano terminou; ao carregar uma página com conteúdo para ler acima do formulário; quando uma validação "ao sair do campo" acusa erro e a pessoa já está no campo seguinte.

Para avisos que não pedem ação imediata ("salvo às 14:32"), use uma região com aria-live="polite", que o leitor anuncia sem tirar o foco do lugar.

Como fazer no Blazor

  1. Guarde uma referência ao primeiro campo com @ref.
  2. No primeiro render, chame FocusAsync no elemento desse campo.
  3. Trate o OnInvalidSubmit do EditForm marcando que o foco precisa ir para o erro.
  4. No render seguinte, quando as classes de erro já estiverem no HTML, foque o primeiro campo com a classe invalid.
  5. Ligue cada mensagem ao campo, para o leitor de tela anunciar o erro junto.
@inject IJSRuntime JS

<EditForm Model="cliente" OnValidSubmit="Salvar" OnInvalidSubmit="AoFalhar" id="form-cliente">
    <DataAnnotationsValidator />

    <label for="nome">Nome completo</label>
    <InputText id="nome" @ref="campoNome" @bind-Value="cliente.Nome"
               aria-describedby="erro-nome" />
    <div id="erro-nome"><ValidationMessage For="() => cliente.Nome" /></div>

    <label for="cpf">CPF</label>
    <InputText id="cpf" @bind-Value="cliente.Cpf" inputmode="numeric"
               aria-describedby="erro-cpf" />
    <div id="erro-cpf"><ValidationMessage For="() => cliente.Cpf" /></div>

    <button type="submit">Salvar</button>
</EditForm>

@code {
    Cliente cliente = new();
    InputText? campoNome;
    bool focarErro;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && campoNome?.Element is ElementReference el)
        {
            await el.FocusAsync();
            return;
        }
        if (focarErro)
        {
            focarErro = false;
            await JS.InvokeVoidAsync("focarPrimeiroInvalido", "#form-cliente");
        }
    }

    void AoFalhar(EditContext contexto) => focarErro = true;

    async Task Salvar() { /* grava e navega */ }
}
// wwwroot/js/foco.js, incluído na página principal
window.focarPrimeiroInvalido = function (seletorDoForm) {
    var campo = document.querySelector(seletorDoForm + " .invalid");
    if (campo) {
        campo.focus();
    }
};

Por que não focar direto no AoFalhar? Porque, naquele momento, o Blazor ainda não renderizou as classes de erro. O OnAfterRenderAsync roda depois que o HTML novo está na página, e a classe invalid, que o Blazor coloca nos inputs com mensagem de validação, já pode ser encontrada. Abra as ferramentas do navegador e confira no HTML gerado quais classes e atributos a sua versão aplica, antes de confiar no seletor.

O FocusAsync no Element do InputText é o que a pergunta do Stack Overflow original procurava: os componentes de input expõem o elemento HTML por essa propriedade, e o foco é aplicado sem JavaScript seu.

O resultado, no cadastro do exemplo: a atendente envia com o CPF em branco, o cursor vai para o campo CPF, e o leitor de tela anuncia "CPF, editar texto, o campo CPF é obrigatório". Ela digita e envia de novo, sem procurar nada na página.

A mesma ideia sem Blazor

Em HTML com JavaScript, o padrão é igual: no submit, valide, e se houver erro, cancele o envio e foque o primeiro campo com aria-invalid="true". O atributo autofocus resolve o foco inicial em páginas de formulário, sem script. Para os demais detalhes de rótulo, mensagem e botão, veja formulário que não cansa.

Erros comuns

  • Mensagem de erro sem foco. A pessoa de teclado não acha o campo, e a de leitor de tela não sabe que houve erro.
  • Foco no resumo de erros sem links. O leitor lê a lista, mas a pessoa ainda precisa caçar cada campo com Tab.
  • Foco automático numa página com texto antes do formulário. O leitor de tela pula o conteúdo, e a página rola para baixo sem pedido.
  • Roubar o foco quando um resultado assíncrono chega. A pessoa estava digitando e as próximas letras caem em outro lugar.
  • Focar antes do render. O seletor não encontra nada e o código parece "não funcionar às vezes".
  • Remover o anel de foco no CSS. O foco vai para o lugar certo, mas ninguém vê onde ele está.

Checklist

  • Página de formulário abre com o foco no primeiro campo.
  • Envio com erro leva o foco ao primeiro campo inválido.
  • Cada mensagem de erro está ligada ao campo por aria-describedby.
  • Nenhuma mudança de foco enquanto a pessoa digita.
  • Avisos sem ação imediata em região aria-live="polite".
  • Anel de foco visível em todos os temas.
  • Teste completo feito só com teclado, e um com leitor de tela (NVDA ou VoiceOver).

Abra o seu formulário, desligue o mouse e envie com o CPF em branco. Onde está o anel de foco depois do Enter? Se estiver no botão ou no topo da página, a mensagem existe e o caminho até ela não. E a pergunta seguinte: ao corrigir o campo e enviar de novo, a pessoa precisa voltar de Tab em Tab até o botão?

Avalie este artigo

Referência: https://stackoverflow.com/questions/59137973/how-to-set-the-focus-to-an-inputtext-element