Personalizar etiqueta <select>

Lista HTML desplegable y personalizable


Tradicionalmente, HTML incorpora una etiqueta HTML <select> que permite crear sencillas listas desplegables para nuestras páginas. Sin embargo, históricamente siempre han sido muy limitadas en cuanto a personalización:

  • 1️⃣ Sólo permiten cambiar estilos concretos y muchos dependen del sistema operativo.
  • 2️⃣ No permiten incluir nada más que texto en las opciones.
  • 3️⃣ El estilizado de opciones es muy limitado.

Recientemente, se está trabajando en permitir una personalización más profunda en estas etiquetas <select> y conseguir alcanzar posibilidades que antes eran muy complicadas o había que implementar desde cero.

Personalizar la etiqueta <select>

Al momento de escribir este post, sólo Canary Chrome 135+ soporta esta característica.

Por defecto, las capacidades modernas de personalización en la etiqueta HTML <select> están desactivadas, por lo que necesitaremos escribir lo siguiente en nuestro CSS para habilitarlas:

select,
::picker(select) {
  appearance: base-select;
}

Esto significa que las etiquetas <select> y el «picker» del <select> (ventana para escoger opciones) se podrán personalizar desde un estilo básico y permitirá personalizaciones profundas, que por defecto están desactivadas.

El esquema general de un <select> moderno y personalizado, sería el siguiente:

appearance: base-select (select HTML personalizado)

Veamoslo en un ejemplo en acción, comparándolo con el modelo clásico:

<div>
  Clásico:
  <select>
    <option>CSS</option>
    <option>Sass</option>
    <option>PostCSS</option>
    <option>Lightning</option>
    <option>PandaCSS</option>
    <option>TailwindCSS</option>
  </select>
</div>

<div>
  Moderno:
  <select class="modern">
    <button>
      Elegiste: <selectedcontent></selectedcontent>
    </button>
    <option>CSS</option>
    <option>Sass</option>
    <option>PostCSS</option>
    <option>Lightning</option>
    <option>PandaCSS</option>
    <option>TailwindCSS</option>
  </select>
</div>
body {
  display: flex;
  gap: 2rem;
}

select.modern,
select.modern::picker(select) {
  appearance: base-select;
}

select {
  font-family: "Victor Mono";
  font-size: 1rem;
  background: #222;
  color: #eee;
  border-radius: 5px;
  width: 275px;

  & option {
    background: #222;
    color: #eee;
  }
}

Ahora veamos poco a poco sus características por separado.

La opción elegida

Observa que en esta versión moderna de <select>, al principio, hemos añadido un elemento <button>. Este botón no es obligatorio añadirlo, pero si se añade, será la parte visible de nuestra lista desplegable, de modo que podemos personalizarlo, añadir contenido (de texto, por ejemplo) y utilizar la etiqueta HTML especial <selectedcontent>, que el navegador reemplazará por la opción escogida por el usuario.

<select>
  <button>
    Elegiste: <selectedcontent></selectedcontent>
  </button>
  <!-- opciones -->
</select>

Obviamente, si no queremos contenido de texto adicional, simplemente lo omitiremos. El elemento <selectedcontent> sólo funciona en el interior del elemento <button>, dentro de un <select>. No puede tener contenido, ya que el navegador clona en su interior la opción elegida cuando el usuario selecciona una.

Incluir imágenes en el <select>

Una de las grandes limitaciones clásicas de los elementos <select> era la imposibilidad de añadir imágenes en las opciones. Con estas nuevas capacidades de personalización, podemos añadir elementos HTML como imágenes <img> o <svg> (por ejemplo) en las opciones de la lista:

<select>
  <button>
    Elegiste: <selectedcontent></selectedcontent>
  </button>
  <option><svg><use href="/assets/icons/logos.svg#css" /></svg> CSS</option>
  <option><svg><use href="/assets/icons/logos.svg#sass" /></svg> Sass</option>
  <option><svg><use href="/assets/icons/logos.svg#postcss" /></svg> PostCSS</option>
  <option><svg><use href="/assets/icons/logos.svg#lightningcss" /></svg> Lightning</option>
  <option><svg><use href="/assets/icons/logos.svg#panda-css" /></svg> PandaCSS</option>
  <option><svg><use href="/assets/icons/logos.svg#tailwind" /></svg> TailwindCSS</option>
</select>
body {
  min-height: 300px;
}

select, ::picker(select) {
  appearance: base-select;
}

select {
  font-family: "Victor Mono";
  font-size: 1rem;
  background: #222;
  color: #eee;
  border-radius: 5px;
  width: 275px;

  & button {
    display: flex;
    align-items: center;
  }

  & svg {
    width: 32px;
    aspect-ratio: 1;
  }

  & selectedcontent {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem;
  }

  & option {
    padding: 5px 10px;
    background: #222;
    color: #fff;

    &:hover {
      background: indigo;
    }
  }

  &::picker-icon {
    display: flex;
    align-items: center;
  }
}

Con algo más de HTML y CSS, podemos incluso crear diseños un poco menos convencionales y conseguir una alta personalización. Por ejemplo, fijate en el siguiente código, donde hemos creado un <div> con clase .container para convertirlo en un grid de 4 celdas:

<select>
  <button>
    Elegiste: <selectedcontent></selectedcontent>
  </button>
  <div class="container">
    <div>
      <option><svg><use href="/assets/icons/logos.svg#css" /></svg> CSS</option>
      <option><svg><use href="/assets/icons/logos.svg#sass" /></svg> Sass</option>
    </div>
    <div>
      <option><svg><use href="/assets/icons/logos.svg#postcss" /></svg> PostCSS</option>
      <option><svg><use href="/assets/icons/logos.svg#lightningcss" /></svg> Lightning</option>
    </div>
    <div>
      <option><svg><use href="/assets/icons/logos.svg#panda-css" /></svg> PandaCSS</option>
      <option><svg><use href="/assets/icons/logos.svg#tailwind" /></svg> TailwindCSS</option>
    </div>
  </div>
</select>
body {
  min-height: 300px;
}

select, ::picker(select) {
  appearance: base-select;
  margin: 0.25rem 0;
  width: 375px;
}

select {
  font-family: "Victor Mono";
  font-size: 1rem;
  background: #222;
  color: #eee;
  border-radius: 0;
  border: 0;

  & .container {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
    background: #222;
  }

  & button {
    display: flex;
    align-items: center;
  }

  & svg {
    width: 32px;
    aspect-ratio: 1;
  }

  & selectedcontent {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem;
  }

  & option {
    padding: 5px 10px;
    color: #fff;

    &:hover {
      background: indigo;
    }
  }

  &::picker-icon {
    display: flex;
    align-items: center;
  }
}

Obviamente, corre de nuestra cuenta personalizar al máximo el diseño de la lista desplegable.

El elemento seleccionado

Habíamos comentado que con la etiqueta <button> junto a la etiqueta <selectedcontent> podemos personalizar el botón donde pulsamos para desplegar la lista, mientras que con el pseudoelemento ::picker(select) podemos personalizar la ventana de opciones que aparece al pulsar en la lista desplegable.

Pero nos quedan algunos consejos más de personalización:

ElementoDescripción
::picker(select)Ventana desplegable al pulsar sobre un <select> personalizado.
option:hoverAplica estilos cuando se mueve el ratón por encima de una opción.
::checkmarkAplica estilos al check del elemento seleccionado. Con content se puede reemplazar.
::picker-iconAplica estilos a la flecha del botón desplegable.

Mientras que con el pseudoelemento ::picker-icon podemos personalizar la flecha que aparece a la derecha del <select> para desplegar la lista, con el pseudoelemento ::checkmark podemos modificar el signo de verificado que aparece por defecto en los elementos seleccionados.

<select>
  <button>
    <selectedcontent></selectedcontent>
  </button>
  <option selected disabled>—Elige una opción—</option>
  <option><svg><use href="/assets/icons/logos.svg#css" /></svg> CSS</option>
  <option><svg><use href="/assets/icons/logos.svg#sass" /></svg> Sass</option>
  <option><svg><use href="/assets/icons/logos.svg#postcss" /></svg> PostCSS</option>
  <option><svg><use href="/assets/icons/logos.svg#lightningcss" /></svg> Lightning</option>
  <option><svg><use href="/assets/icons/logos.svg#panda-css" /></svg> PandaCSS</option>
  <option><svg><use href="/assets/icons/logos.svg#tailwind" /></svg> TailwindCSS</option>
</select>
body {
  min-height: 375px;
}

select, ::picker(select) {
  appearance: base-select;
}

select {
  font-family: "Victor Mono";
  font-size: 1rem;
  background: #222;
  color: #eee;
  border-radius: 5px;
  width: 275px;

  & button {
    display: flex;
    align-items: center;
  }

  & svg {
    width: 32px;
    aspect-ratio: 1;
  }

  & selectedcontent {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem;
  }

  & option {
    padding: 5px 10px;
    background: #222;
    color: #fff;

    &:hover {
      background: indigo;
    }
  }

  & option:checked {
    background: #000;
  }

  & ::checkmark {
    content: "->";
    color: lime;
    font-weight: bold;
  }

  &::picker-icon {
    display: flex;
    justify-content: center;
    align-items: center;
    border-left: 1px solid #666;
    padding-left: 0.4rem;
  }
}

Las pseudoclases :open y :closed

Por último, recuerda que puedes utilizar las pseudoclases :open y :closed para indicar cuando la lista está desplegada (open) o no (closed).

Veamos un ejemplo donde vamos a añadir una transición con un color de fondo en el botón del desplegable:

<select>
  <button>
    <selectedcontent></selectedcontent>
  </button>
  <option selected disabled>—Elige una opción—</option>
  <option><svg><use href="/assets/icons/logos.svg#css" /></svg> CSS</option>
  <option><svg><use href="/assets/icons/logos.svg#sass" /></svg> Sass</option>
  <option><svg><use href="/assets/icons/logos.svg#postcss" /></svg> PostCSS</option>
  <option><svg><use href="/assets/icons/logos.svg#lightningcss" /></svg> Lightning</option>
  <option><svg><use href="/assets/icons/logos.svg#panda-css" /></svg> PandaCSS</option>
  <option><svg><use href="/assets/icons/logos.svg#tailwind" /></svg> TailwindCSS</option>
</select>
body {
  min-height: 375px;
}

select, ::picker(select) {
  appearance: base-select;
}

select {
  min-width: 375px;
  transition: background 1s ease;
  background: #222;
  color: #fff;

  & selectedcontent {
    display: flex;
    align-items: center;
    gap: 1rem;
  }

  &::picker-icon {
    display: flex;
    align-items: center;
  }
  & button {
    display: flex;
    align-items: center;
  }

  & svg {
    width: 32px;
    aspect-ratio: 1;
  }

  &:open {
    background: indigo;
  }
}

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev