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>
One future | Evento online e gratuito de programação | Rocketseat
Descubra como ganhar tempo para acelerar na sua carreira em programação através de uma oportunidade nunca antes vista e celebre o aniversário da Rocketseat junto a comunidade.

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;
}
One future | Evento online e gratuito de programação | Rocketseat
Descubra como ganhar tempo para acelerar na sua carreira em programação através de uma oportunidade nunca antes vista e celebre o aniversário da Rocketseat junto a comunidade.

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 absoluteResultado 👇

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?

One future | Evento online e gratuito de programação | Rocketseat
Descubra como ganhar tempo para acelerar na sua carreira em programação através de uma oportunidade nunca antes vista e celebre o aniversário da Rocketseat junto a comunidade.
#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.

One future | Evento online e gratuito de programação | Rocketseat
Descubra como ganhar tempo para acelerar na sua carreira em programação através de uma oportunidade nunca antes vista e celebre o aniversário da Rocketseat junto a comunidade.

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.