HTML e CSS: Criando um Caixa de seleção personalizada
Quando o assunto é desenvolvimento web, frequentemente precisamos personalizar elementos de interface para melhorar a experiência do usuário e integrar o design visual do site. Neste artigo, vamos criar um componente de seleção (dropdown) personalizado que não só parece bom mas também é acessível.
Estruturando o HTML inicialComece por definir a estrutura básica do seu componente de seleção. Vamos utilizar um input
tipo checkbox para controlar a visibilidade da lista de opções e ícones para melhorar a experiencia de usuário. Aqui usamos a biblioteca de ícones lucide-react: https://lucide.dev/guide/packages/lucide
<div class="select">
<div id="category-select">
<label for="options-view-button">Categoria</label>
<input type="checkbox" id="options-view-button" />
<div id="select-button">
<div id="selected-value">Selecione a categoria</div>
<div id="chevrons">
<i data-lucide="chevron-down"></i>
<i data-lucide="chevron-up"></i>
</div>
</div>
</div>
</div>
<script src="<https://unpkg.com/lucide@latest>"></script>

Aplicando CSS para EstilizaçãoA estilização é crucial para garantir que o componente de seleção não só funcione bem, mas também tenha uma boa aparência. Aqui está o CSS básico para começar:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.select {
padding: 6rem;
}
#category-select label {
font-size: 0.75rem;
letter-spacing: 0.0225rem;
}
#select-button {
margin-top: 0.5rem;
display: flex;
padding: 0.75rem;
align-items: center;
justify-content: space-between;
border-radius: 0.375rem;
border: 1px solid #252529;
background-color: #17171a;
}
Com isso você terá esse resultado 👇

Agora vamos estilizar a parte interna do nosso input
com mais CSS dando mais sentido a essa caixa de seleção, até porque aqueles dois ícones não fazem sentido juntos. Sem muito mistério vamos pro CSS:
#selected-value {
color: #afabb6;
font-size: 0.875rem;
letter-spacing: 0.02625rem;
}
#chevrons svg {
width: 1rem;
height: 1rem;
}
#chevrons {
color: #afabb6;
}
#chevrons [data-lucide='chevron-up'] {
display: none;
}
Resultado👇

Agora vamos explodir nossa cabeça com mais um pouco de CSS. A ideia é dar, a nossa caixa de seleção, mais vida e funcionalidades:
#options-view-button:focus + #select-button,
#options-view-button:checked + #selected-button {
outline: 1px solid #a881e6;
}
#category-select:has(#options-view-button:checked) label,
#options-view-button:checked
+ #select-button
#chevrons
[data-lucide='chevron-down'] {
color: #a881e6;
}
#options-view-button:checked
+ #select-button
#chevrons
[data-lucide='chevron-down'] {
display: none;
}
#options-view-button:checked
+ #select-button
#chevrons
[data-lucide='chevron-up'] {
display: block;
}

Com isso adicionamos estilos condicionais ao componente de seleção personalizado. Quando o checkbox é focado ou marcado, o botão de seleção é destacado com uma borda. Além disso, a cor dos ícones de seta muda e a seta para baixo é ocultada enquanto a seta para cima é mostrada, indicando que o menu de seleção está ativo ou expandido.
Resultado 👇

Com Um pouco mais de CSS, vamos controlar comportamento visual do checkbox
representado pelo #options-view-button
dentro do nosso componente de seleção customizado:
**#category-select {
position: relative;
}
#options-view-button {
all: unset;
position: absolute;
inset: 0;
cursor: pointer;
z-index: 3;
}**
Embora o checkbox
esteja invisível, você vai perceber que toda a área do #category-select
funcione como um botão interativo que, quando clicado, pode exibir ou esconder outros elementos, como uma lista de categorias, indicada pelos ícones de setas para cima e para baixo. Isso acontece porque estamos usando a propriedade position
e seus valores: relative
e absolute
Resultado 👇

Vamos construir as opções de dentro do nosso componente de seleção. Aqui está aqui está o trecho do HTML, repita algumas vezes e mude os icones, label e value como desejar:
<ul id="options">
<li class="option">
<input
type="radio"
name="category"
value="vegetable"
data-label="Legume"
/>
<i data-lucide="carrot"></i>
<span class="label">Legumes</span>
<i data-lucide="check"></i>
</li>
<li class="option">
<input
type="radio"
name="category"
value="bakery"
data-label="Padaria"
/>
<i data-lucide="sandwich"></i>
<span class="label">Pães</span>
<i data-lucide="check"></i>
</li>
</ul>
Após fazer isso você terá um resultado parecido com esse:

Vamos estilizar?

#options {
margin-top: 0.25rem;
border-radius: 0.375rem;
background: #17171a;
}
.option {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
border-bottom: 1px solid #252529;
}
.option .label {
color: #fbf9fe;
}
.option svg {
width: 1rem;
height: 1rem;
}
Com isso facilitamos a identificação das opções por parte do usuário e permitimos uma navegação mais intuitiva e agradável visualmente 😉

Hmmm, porém acredito que podemos melhorar, vamos jogar o último icone (o check) para o canto direito? Bem simples:
#option svg:last-child {
margin-left: auto;
color: #a881e6;
}
Aqui usamos o pseudo-elemento last-child
para pegar o último svg
de cada <li>
Ainda sobre CSS, que tal darmos feedbacks imediatos aos usuários que interagirem com nosso componente?
.option svg:last-child {
margin-left: auto;
color: #A881E6;
}
.option:has(input:checked),
.option:hover {
border-bottom: 1px solid #252529;
background-color: #252529;
}
.option:has(input:focus) {
outline: 1px solid #A881E6;
}
.option [data-lucide="check"] {
display: none;
}
.option:has(input:checked) [data-lucide="check"] {
display: block;
}
Resultado:

E se você usar os teclas de navegação do seu teclado você vai que a acessibilidade está presente, testa aí.
E, após isso, você precisa aplicar a propriedade position
com valor relative
na classe .option
. Pois, vamos deixar nossos input
de tipo radio
absoluto em relação ao option:
.option input[type="radio"] {
all: unset;
position: absolute;
inset: 0;
cursor: pointer;
}
Olha a mágica 👇

Deu vontade, vou colocar algumas cores no ícones:
.option:nth-child(1) {
color: #BB9F3A;
}
.option:nth-child(2) {
color: #8CAD51;
}
.option:nth-child(3) {
color: #DB5BBF;
}
.option:nth-child(4) {
color: #E07B67;
}
.option:nth-child(5) {
color: #7B94CB;
}

Nesse caso, quando usamos usando o pseudo-classe :nth-child
cada regra CSS aplica uma cor única ao conteúdo de cada elemento .option
com base na sua ordem no contêiner pai.

O próximo passo agora é fazer com que a partir do estado da nossa caixa de seleção os nossos inputs do tipo radio altere a visibilidade e a cor. Ah, mas antes de tudo, vá no identificador #options
e defina um propriedade display: none;
para que os nossos inputs radio não sejam visíveis.
Após isso:
#category-select:has(#options-view-button:checked) + #options {
display: block;
}
.select:has(.option input:checked) #category-select label {
color: #A881E6;
}
.select:has(.option input:checked) #category-select {
color: #FBF9FE;
}
Nas regras CSS que definimos aqui usam o pseudo-classe :has
para estilizar elementos baseando-se no estado de seus elementos filhos, que é muito útil quando precisamos manipular a aparência dos elementos dependendo das interações do usuário.

Com isso finalizamos a parte visual de um componente de seleção personalizado. Aqui fizemos passo a passo para oferecer uma base sólida para criar um componente de seleção personalizado que não apenas se integra esteticamente com o design do seu site, mas também acessibilidade.
Experimente adicionar mais estilos e funcionalidades conforme necessário para atender às suas necessidades específicas. Com essas habilidades, você pode melhorar significativamente a interação do usuário em seu site.