Programação Por Equipe DevBras

Capa do artigo: Barra de progresso acessível: quem não vê a cor precisa saber quanto falta

Barra de progresso acessível: quem não vê a cor precisa saber quanto falta

Faça uma barra de progresso que diga o percentual em texto, anuncie o andamento ao leitor de tela sem metralhar avisos e troque a barra por uma mensagem clara quando a operação falhar.

A importação de 10.000 clientes começa e uma faixa azul cresce devagar na tela. Aos 40%, ela para. Travou ou está processando a parte pesada? Quem usa leitor de tela não sabe nem que existe uma faixa. Quem tem baixa visão vê um retângulo sem número. Quem enxerga bem fica olhando a cor e torcendo.

A resposta curta: a barra é decoração; a informação é o texto. Mostre o percentual (ou "4.200 de 10.000") escrito ao lado, use o elemento <progress> ou role="progressbar" com os atributos ARIA de valor, anuncie marcos (não cada ponto percentual) numa região aria-live e, quando não souber o fim, diga isso em vez de fingir um número.

Por que a barra bonita fica muda

Componentes prontos, como os de bibliotecas Bootstrap para Blazor, desenham uma div dentro de outra e mudam a largura da interna. Para o olho, isso é progresso. Para a árvore de acessibilidade, que é o que o leitor de tela lê, uma div sem papel nem nome não é nada. A demonstração da biblioteca mostra o controle funcionando; ela não sabe o que a sua barra mede, então não pode dar nome a ela por você.

O segundo problema é a cor. Verde para sucesso, vermelho para erro e azul para andamento só funcionam para quem distingue essas cores, num monitor calibrado, sem reflexo do sol. Cor pode reforçar uma mensagem, mas não pode ser a única forma de transmiti-la. O terceiro problema é o tempo: uma barra animada para sempre, sem número, não diferencia "falta pouco" de "travou há dez minutos".

O mínimo que uma barra precisa comunicar

  • O que está sendo medido: "Importando clientes.csv", não só "Progresso".
  • Quanto já foi: percentual ou contagem, em texto visível.
  • Se o fim é conhecido: barra determinada com valor, ou indeterminada com a palavra "processando".
  • Como terminou: mensagem de sucesso ou de erro em texto, com o que fazer a seguir.

Como fazer certo, passo a passo

  1. Prefira o elemento nativo <progress> quando ele resolver. Ele já tem o papel de barra de progresso, e sem o atributo value vira indeterminado automaticamente.
  2. Dê um nome à barra com <label for>, aria-label ou aria-labelledby.
  3. Escreva o andamento em texto visível ao lado da barra. Isso serve para todo mundo, inclusive quem está no celular sob o sol.
  4. Se usar uma div customizada, coloque role="progressbar", aria-valuemin, aria-valuemax e aria-valuenow. Quando o valor for desconhecido, omita aria-valuenow.
  5. Use aria-valuetext quando o número cru não fizer sentido, como "4.200 de 10.000 linhas".
  6. Anuncie marcos, não cada atualização. Uma região aria-live="polite" atualizada a cada 25%, ou a cada etapa, informa sem interromper a pessoa o tempo todo.
  7. Na falha, troque a barra por uma mensagem com role="alert", dizendo o que deu errado e o que a pessoa pode fazer.

A versão em HTML puro

<label for="importacao">Importando clientes.csv</label>
<progress id="importacao" max="100" value="42">42%</progress>
<p>42%: 4.200 de 10.000 linhas</p>

<!-- fim desconhecido: sem value, a barra é indeterminada -->
<label for="analise">Analisando o arquivo</label>
<progress id="analise" max="100">Processando</progress>
<p>Processando. Isso costuma levar menos de um minuto.</p>

O texto dentro de <progress> é um fallback; o que realmente garante a informação é o parágrafo visível. Não confie no conteúdo interno do elemento para quem enxerga.

Um componente Blazor que não fica mudo

No exemplo da importação, o servidor informa linhas processadas e total. O componente calcula o percentual, decide quando anunciar e trata a falha. Se a biblioteca que você usa tiver parâmetros de rótulo e texto, configure-os; se não tiver, inspecione o HTML gerado no navegador e confira se o papel e os atributos aparecem. Quando não aparecem, um componente seu, como o abaixo, é mais honesto:

@* BarraDeProgresso.razor *@
@if (Erro is not null)
{
    <p role="alert">Falha na importação: @Erro</p>
}
else
{
    <p id="rotulo-importacao">@Rotulo</p>
    <div role="progressbar"
         aria-labelledby="rotulo-importacao"
         aria-valuemin="0"
         aria-valuemax="100"
         aria-valuenow="@Percentual"
         aria-valuetext="@Texto"
         class="trilho">
        <div class="preenchido" style="width: @(Percentual ?? 0)%"></div>
    </div>
    <p>@Texto</p>
    <p aria-live="polite" class="somente-leitor">@anuncio</p>
}

@code {
    [Parameter, EditorRequired] public string Rotulo { get; set; } = "";
    [Parameter] public int? Processadas { get; set; }
    [Parameter] public int? Total { get; set; }
    [Parameter] public string? Erro { get; set; }

    private string anuncio = "";
    private int ultimoMarco = -1;

    private int? Percentual => Processadas is int p && Total is int t && t > 0
        ? Math.Min(100, p * 100 / t)
        : null;

    private string Texto => Percentual is int pct
        ? $"{pct}%: {Processadas:N0} de {Total:N0} linhas"
        : "Processando";

    protected override void OnParametersSet()
    {
        if (Percentual is not int pct) return;
        var marco = pct / 25;
        if (marco != ultimoMarco)
        {
            ultimoMarco = marco;
            anuncio = pct >= 100 ? "Importação concluída" : $"{pct}% concluído";
        }
    }
}

Três detalhes fazem a diferença. Quando Percentual é nulo, o Blazor não renderiza aria-valuenow, e a barra passa a ser indeterminada para a tecnologia assistiva, sem código extra. O texto visível e o aria-valuetext dizem a mesma coisa, então quem vê e quem ouve recebem a mesma informação. E o anúncio só muda a cada 25%: numa importação que atualiza a cada 50 linhas, anunciar tudo seriam 200 interrupções.

A classe somente-leitor é aquela regra de CSS que esconde o texto visualmente sem tirá-lo da árvore de acessibilidade. Não use display: none nela, porque aí o leitor de tela também ignora.

Animação, cor e o momento de terminar

Listras animadas e brilho que corre podem incomodar quem tem sensibilidade a movimento. Respeite prefers-reduced-motion no CSS e desligue a animação decorativa quando a pessoa pediu menos movimento. A barra continua avançando; só o enfeite para.

Quando a operação termina, não deixe a barra verde parada como única notícia. Escreva "10.000 clientes importados" e ofereça o próximo passo, como "Ver clientes". Se a barra some e nada aparece no lugar, quem usa leitor de tela não sabe se terminou ou se a página quebrou. O mesmo vale para upload, com uma pegadinha extra: a barra chegar a 100% significa que os bytes saíram do navegador, não que o servidor gravou o arquivo, como explica o post sobre barra de upload que não é sucesso.

Erros comuns

  • Barra sem nome. O leitor de tela anuncia "barra de progresso, 42%" sem dizer do quê; com duas barras na página, a pessoa não sabe qual é qual.
  • Só cor para sucesso e erro. Quem não distingue vermelho de verde vê uma barra cheia nos dois casos e acha que a importação deu certo.
  • Atualizar a região aria-live a cada 1%. O leitor fala sem parar, atropela o que a pessoa estava lendo e ela desliga o som ou abandona a tela.
  • Percentual inventado. Avançar até 90% por tempo e esperar ali faz a pessoa confiar num número falso e recarregar a página no pior momento.
  • Animação infinita sem texto. Spinner girando por cinco minutos não diferencia lentidão de travamento.
  • Remover a barra na falha sem mensagem. A tela volta ao estado inicial e a pessoa repete a operação, às vezes duplicando dados.

Checklist

  • Toda barra tem um nome que diz o que está sendo medido.
  • O andamento aparece em texto visível, não só na largura.
  • Barras customizadas têm role="progressbar" e os atributos de valor; as indeterminadas omitem aria-valuenow.
  • Os anúncios para leitor de tela acontecem em marcos, não a cada atualização.
  • Sucesso e erro são comunicados em texto, com o próximo passo.
  • A animação decorativa respeita prefers-reduced-motion.
  • Você testou com um leitor de tela (NVDA no Windows, VoiceOver no Mac ou no iPhone) pelo menos uma vez.

Abra a tela mais demorada do seu sistema, desligue o monitor (ou ative o leitor de tela) e inicie a operação: sem olhar a cor, dá para saber quanto falta e se terminou?

Avalie este artigo

Referência: https://demos.blazorbootstrap.com/progress