Programação Por Equipe DevBras

Capa do artigo: Botão de escolher arquivo personalizado que ainda funciona no teclado

Botão de escolher arquivo personalizado que ainda funciona no teclado

Estilize o input de arquivo com ::file-selector-button ou com um label, sem tirá-lo da ordem de tabulação, e mostre o nome, o tipo aceito e o limite antes do envio.

O formulário de vagas ficou bonito: um botão azul "Enviar currículo" no lugar do retângulo cinza do navegador. Uma candidata que navega só pelo teclado aperta Tab, pula do campo de telefone direto para "Enviar candidatura" e nunca encontra onde anexar o arquivo. O input foi escondido com display: none. A resposta curta: estilize o próprio botão com ::file-selector-button, ou esconda o input apenas visualmente, mantendo-o focável, e desenhe o foco no label. Depois da escolha, mostre o nome do arquivo em texto.

Este guia monta esse campo do zero para o formulário de vagas: PDF ou DOCX, até 5 MB, com o nome do arquivo visível e um teste de teclado no fim. O princípio geral está em botão bonito de arquivo não substitui o input de verdade; aqui o foco é a receita.

Por que o botão some para o teclado

O <input type="file"> é o único elemento que abre o seletor de arquivos do sistema. Ele recebe foco com Tab e abre o diálogo com Espaço ou Enter, conforme o navegador. Um label ligado a ele também abre o diálogo quando clicado, e é por isso que a técnica de "esconder o input e estilizar o label" parece funcionar.

O problema está em como se esconde. display: none e visibility: hidden tiram o elemento da árvore de acessibilidade e da ordem de tabulação. O label não recebe foco por conta própria, então sobra um botão que só existe para o mouse. Com required, piora: o navegador tenta apontar o campo inválido e não consegue focar um elemento invisível.

Opção 1: estilizar o botão nativo

Os navegadores atuais expõem o botão interno do campo como o pseudo-elemento ::file-selector-button. Você muda cor, borda e espaçamento sem esconder nada. O texto ao lado ("Nenhum arquivo escolhido") continua vindo do navegador, no idioma dele.

input[type="file"]::file-selector-button {
  padding: 0.6rem 1.2rem;
  margin-right: 0.75rem;
  border: 0;
  border-radius: 6px;
  background: #1a5fb4;
  color: #fff;
  font: inherit;
  cursor: pointer;
}

input[type="file"]:focus-visible {
  outline: 3px solid #1a5fb4;
  outline-offset: 3px;
}

É a opção com menos risco. Se o layout aceita o texto padrão do navegador ao lado do botão, pare aqui.

Opção 2: input escondido só visualmente, label como botão

Quando o design pede controle total, use a técnica de ocultação visual: o elemento fica com 1 pixel, recortado, mas continua no fluxo de foco e na árvore de acessibilidade.

  1. Escreva o input com id, name e accept. O accept filtra o diálogo; não é validação.
  2. Coloque o label logo depois do input, com um texto que diga o que se anexa, não só "Escolher".
  3. Escreva tipo e limite antes da escolha, num parágrafo ligado ao input por aria-describedby.
  4. Aplique a classe de ocultação visual no input e desenhe o foco no label com o seletor de irmão adjacente.
  5. Mostre o nome do arquivo escolhido numa área com aria-live="polite", para o leitor de tela anunciar a mudança.
  6. Confira tamanho no navegador para dar retorno rápido, e confira de novo no servidor, que é quem decide.

O HTML

<input type="file" id="curriculo" name="curriculo"
       accept=".pdf,.docx" required
       class="visualmente-oculto"
       aria-describedby="curriculo-ajuda curriculo-nome">
<label for="curriculo" class="botao-arquivo">Anexar currículo</label>
<p id="curriculo-ajuda">PDF ou DOCX, até 5 MB.</p>
<p id="curriculo-nome" aria-live="polite">Nenhum arquivo escolhido.</p>

O CSS

.visualmente-oculto {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.botao-arquivo {
  display: inline-block;
  padding: 0.6rem 1.2rem;
  border-radius: 6px;
  background: #1a5fb4;
  color: #fff;
  cursor: pointer;
}

.visualmente-oculto:focus-visible + .botao-arquivo {
  outline: 3px solid #1a5fb4;
  outline-offset: 3px;
}

O último seletor é o que salva o teclado. O foco está no input, que é invisível, mas o contorno aparece no label logo ao lado. Sem essa regra, a pessoa chega no campo e não vê onde está. Se o site tem tema escuro, confira o contraste do contorno nos dois temas, como em anel de foco no tema escuro.

O JavaScript que mostra o nome e confere o tamanho

const campo = document.getElementById('curriculo');
const nome = document.getElementById('curriculo-nome');
const LIMITE = 5 * 1024 * 1024;

campo.addEventListener('change', () => {
  const arquivo = campo.files[0];
  if (!arquivo) {
    nome.textContent = 'Nenhum arquivo escolhido.';
    return;
  }
  if (arquivo.size > LIMITE) {
    nome.textContent = arquivo.name + ' tem mais de 5 MB. Escolha outro arquivo.';
    campo.value = '';
    return;
  }
  const kb = Math.ceil(arquivo.size / 1024);
  nome.textContent = 'Selecionado: ' + arquivo.name + ' (' + kb + ' KB).';
});

Limpar campo.value quando o arquivo passa do limite evita que um arquivo recusado siga no envio. Atribuir string vazia é a única escrita permitida nesse campo; o navegador não deixa o script escolher um arquivo no lugar da pessoa.

Vários arquivos

Com o atributo multiple, campo.files vira uma lista. Mostre todos os nomes, não só a contagem, e diga no texto de ajuda quantos são aceitos. Se a pessoa precisa remover um item antes de enviar, o input sozinho não resolve, porque a lista dele não é editável. Aí entra uma fila mantida em JavaScript, assunto de vários arquivos de uma vez.

O servidor continua dono da regra

O accept=".pdf,.docx" só muda o filtro inicial do diálogo; a pessoa pode trocar para "Todos os arquivos". A checagem de tamanho no navegador pode ser desligada. No backend, confira tamanho, tipo real pelo conteúdo e nome de destino gerado por você. Em PHP, isso está em upload de arquivo em PHP.

Erros comuns

  • Esconder o input com display: none. O campo sai da ordem de Tab e do leitor de tela; só o mouse consegue anexar.
  • Ocultar visualmente sem desenhar o foco no label. O teclado chega no campo, mas a pessoa não vê onde está.
  • Label com texto genérico como "Escolher". O leitor de tela anuncia "Escolher, botão" e ninguém sabe o que anexar.
  • Não mostrar o nome do arquivo escolhido. A pessoa não sabe se a escolha deu certo e anexa duas vezes ou envia sem nada.
  • Informar tipo e limite só na mensagem de erro. A pessoa descobre a regra depois de esperar o envio de um arquivo grande.
  • Tratar accept como validação. Chega executável renomeado no servidor.

Checklist

  • O input não usa display: none nem visibility: hidden.
  • Com Tab, o foco chega no campo e um contorno visível aparece.
  • Espaço ou Enter abre o seletor de arquivos.
  • O label diz o que se anexa.
  • Tipo aceito e tamanho máximo aparecem antes da escolha.
  • O nome do arquivo escolhido aparece em texto e é anunciado.
  • O servidor valida tamanho e tipo real, independentemente do navegador.

Abra o seu formulário, tire a mão do mouse e chegue no campo de arquivo só com Tab. O contorno de foco aparece em cima do botão, e depois de escolher o arquivo você consegue ler o nome dele na tela?

Avalie este artigo

Referência: http://www.scriptbrasil.com.br/forum/topic/169007-duvida-input-file-personalizado/