Volver a las notas

<select> personalizable: ya en Chrome y Safari

13 de noviembre de 2026·
htmlcssselectformularios
Demo en vivoAbrir a pantalla completa

El <select> que por fin se puede diseñar ya no es un experimento solo de Chrome. Safari 27, publicado el 14 de septiembre de 2026, añadió "the customizable <select> element, allowing custom styling with appearance: base-select and custom content with the <selectedcontent> HTML element". Chrome lo tiene desde la versión 135. Así que dos de los tres grandes motores ya te dan un <select> de verdad, con su teclado y su comportamiento de formulario, que puedes diseñar como cualquier otro componente.

Abre la demo de arriba y elige un tema. Los círculos de color, las opciones a dos líneas, la marca de seleccionado, la flecha que gira y el panel que aparece con un fundido son un único <select> nativo. Luego marca "Quitar base-select": el mismo HTML vuelve al control clásico y todo sigue funcionando.

Por qué importa

Durante veinte años, la respuesta a "¿puedo darle estilo a este desplegable?" fue "rehazlo en JavaScript". Eso suponía un div haciéndose pasar por lista, tu propio manejo del teclado, tu propio ARIA y un campo que ya no se enviaba solo con el formulario. El select personalizable mantiene el elemento nativo y solo abre su interior. Como dice el anuncio de Chrome, es "accessible, standardized and CSS styleable".

Cómo funciona

Se activa con una sola propiedad CSS, en el select y en su panel de opciones:

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

MDN aclara que puedes activarlo solo en el select, pero que no puedes activarlo en el panel sin activarlo también en el <select>.

Después, el HTML puede llevar algo más que texto. Un <button> como primer hijo pasa a ser el control cerrado, y <selectedcontent> dentro de él muestra una copia de la opción elegida:

<select id="theme">
  <button><selectedcontent></selectedcontent></button>
  <option value="ember">
    <span class="swatch" aria-hidden="true"></span>
    <span class="txt"><span>Ember</span> <small>richi.codes orange</small></span>
  </option>
  <!-- más opciones -->
</select>

Cada opción de la demo es exactamente eso: un círculo decorativo, un nombre y una descripción corta.

Las piezas a las que das estilo son pseudoelementos y pseudoclases nuevos:

/* la flecha, y cómo gira mientras está abierto */
select::picker-icon { transition: rotate .3s ease; }
select:open::picker-icon { rotate: 180deg; }

/* la opción elegida y su marca */
option:checked { background: color-mix(in oklab, var(--sa) 18%, transparent); }
option::checkmark { order: 1; margin-left: auto; content: "✓"; }

/* la copia en el botón cerrado: oculta la descripción */
selectedcontent small { display: none; }

El panel es un popover en la capa superior (top layer), así que se anima como uno. Esta es la animación de entrada de la demo, con @starting-style para el primer fotograma:

::picker(select) {
  opacity: 0;
  transform: translateY(-8px) scale(.96);
  transition: opacity .25s, transform .35s,
              overlay .35s allow-discrete, display .35s allow-discrete;
}
select:open::picker(select) { opacity: 1; transform: none; }
@starting-style {
  select:open::picker(select) { opacity: 0; transform: translateY(-8px) scale(.96); }
}

Además va anclado de forma implícita al botón, así que puedes recolocarlo con anchor() si lo necesitas.

Soporte y trampas

  • Chrome 135+ y Safari 27; Firefox todavía no. Los datos de compatibilidad de MDN recogen appearance: base-select en Firefox 149 solo detrás de dos preferencias, y no incluyen <selectedcontent> ni ::picker(select). MDN lo marca como de disponibilidad limitada, no Baseline.
  • El plan B es el select clásico. Sin soporte, el navegador ignora el botón y deja las opciones como texto plano. Mete tus estilos en @supports (appearance: base-select) para que el control clásico no quede a medio diseñar. La demo lo hace y, además, en el plan B reescribe el texto de cada opción como "Brasa · Naranja richi.codes" para que el nombre y la descripción no salgan pegados.
  • La regla de oro de WebKit. El equipo de WebKit aconseja "always provide text content or accessible text attributes for your option elements": da siempre texto o atributos de texto accesible a tus option. Los iconos van con aria-hidden="true" o alt="", y el significado, en texto. MDN añade que ::checkmark y ::picker-icon no están en el árbol de accesibilidad, así que lo que pongas en su content no se anuncia.
  • El valor puede cambiar si no usas value. Sin atributo value, el valor del select es el textContent de la opción sin espacios sobrantes. Si añades una descripción dentro de la opción, también cambia lo que se envía. Lo comprobé en Chromium y WebKit con un <option> sin value: el valor siguió al texto. El post de Chrome lo deja claro: las opciones que usan value no se ven afectadas.
  • <selectedcontent> se queda desfasado. Copia la opción cuando cambia la selección, y solo entonces. En Chromium y WebKit cambié el texto de una opción después de cargar: el botón mantuvo el texto viejo hasta que volví a seleccionar. A la propia demo le pasa: traduce las opciones al inglés cuando la página ya está cargada, así que vuelve a seleccionar la opción actual para refrescar la copia. Los frameworks que vuelven a pintar las opciones tienen que hacer lo mismo.
  • La copia no se lleva los estilos de la opción. En la demo, los colores de cada opción van en propiedades personalizadas sobre el <option>. La copia dentro de <selectedcontent> está en otro punto del árbol y no los hereda, así que su círculo toma los colores del tema activo desde la raíz.
  • Comportamientos nativos que pierdes. Chrome los enumera: el select no se pinta fuera del área de la página, no lanza los componentes propios del sistema operativo móvil y deja de tomar el ancho de la opción más larga.

Qué vigilar

  • Firefox: el trabajo está detrás de preferencias. Cuando salga, será Baseline y podrás quitar casi todo el código de plan B.
  • El soporte en frameworks. MDN avisa de que algunos frameworks bloquean estas funciones y otros dan errores de hidratación con renderizado en servidor.
  • Más controles de formulario "base". El post de Chrome dice que quedan más elementos "base" por completar. Este es solo el primero.

Fuentes