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.
- Escreva o input com
id,nameeaccept. Oacceptfiltra o diálogo; não é validação. - Coloque o label logo depois do input, com um texto que diga o que se anexa, não só "Escolher".
- Escreva tipo e limite antes da escolha, num parágrafo ligado ao input por
aria-describedby. - Aplique a classe de ocultação visual no input e desenhe o foco no label com o seletor de irmão adjacente.
- Mostre o nome do arquivo escolhido numa área com
aria-live="polite", para o leitor de tela anunciar a mudança. - 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
acceptcomo validação. Chega executável renomeado no servidor.
Checklist
- O input não usa
display: nonenemvisibility: 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?
Referência: http://www.scriptbrasil.com.br/forum/topic/169007-duvida-input-file-personalizado/