← Biblioteca componentes/conversao/select-custom/ficha.md Abrir previewmarkupestiloscript
conversao/select-custom

Select customizado acessível

🟢 estavel complexidade média prefixo csel- formulario select listbox acessibilidade teclado

Substituto do <select> nativo. Botão que herda a tipografia do campo, seta que gira, lista flutuante estilizável, navegação completa por teclado e ARIA. Escreve num <input type="hidden">, então o formulário envia como qualquer outro campo.

Existe por um motivo só: nenhum navegador deixa estilizar a lista de opções de um <select> nativo. Num formulário escuro e com tipografia de marca, o dropdown do sistema destoa de tudo.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
Label 2 a 4 palavras, caixa alta por CSS: "Capital para investir".
Placeholder "Selecione" mais o substantivo: "Selecione uma faixa". Nunca uma opção real como placeholder.
Opções 3 a 8. Texto curto o bastante para caber em uma linha. Ordem crescente, ou alfabética — nunca "a mais escolhida primeiro".

Faixas de valor são melhores que campo aberto em formulário de qualificação: o visitante não precisa revelar o número exato, e você segmenta igual.


Como usar

<link rel="stylesheet" href="componentes/conversao/select-custom/estilo.css">
<script src="componentes/conversao/select-custom/script.js"></script>

O script varre [data-csel], então vários selects na mesma página funcionam — e abrir um fecha os outros.

Ganchos

Gancho Papel
[data-csel] o campo
[data-csel-required] marca como obrigatório, lido pela validação do formulário
.csel.is-open lista aberta
.csel.has-error dispara o tremor de validação
.csel-val.is-set valor escolhido (muda a cor do placeholder)
input[type=hidden] onde o valor é escrito; o name dele é o que o backend recebe

Teclado

Tecla Fechado Aberto
↓ / Enter / Espaço abre desce / escolhe
sobe
Home / End primeira / última
Esc fecha e devolve o foco ao botão

Gotchas


Histórico

Projeto Onde Data Diferenças
Liberta Wealth index.html, campo "Capital para investir" do #diagnostico 2026-08 Origem. Prefixo cselect-, estado .open, classe de erro .shake, cores #f8f6f2 / #66645e / #1a1a1a / #d4d1ca cruas, sem navegação por teclado dentro da lista (só Esc), sem tabindex nas opções, sem aria-labelledby na lista.

Normalizações aplicadas na extração: prefixo cselect-csel-; .open.is-open, .shake.has-error; cores cruas → --fg / --fg-muted / --dark-2 / --rule-2; adicionada navegação completa por teclado (setas, Home, End, Enter, Espaço) e o estado .is-active que ela precisa; adicionado tabindex="-1" nas opções e aria-labelledby na lista; scrollIntoView({block:'nearest'}) para a lista acompanhar a seta; adicionado fallback de prefers-reduced-motion no erro; wrapper de campo separado do widget, para o select poder viver dentro de qualquer formulário.