El select personalizable y el equilibrio con la accesibilidad
appearance: base-select, ::picker(select) y las piezas nuevas del desplegable nativo: qué funciona en agosto de 2026, y la regla que hace que no rompas nada al adoptarlo.
El select fue durante veinte años la prueba de que el modelo de controles nativos tenía un límite duro: podías estilar el botón, pero el menú desplegable era una superficie del sistema operativo fuera de tu alcance. La respuesta de la industria fue reconstruirlo con div y role="combobox", y el resultado colectivo es un desastre documentado: la inmensa mayoría de esos componentes fallan con teclado, con lector de pantalla o con el teclado virtual del móvil. En 2026 la plataforma está deshaciendo ese camino, y el nombre del arreglo es appearance: base-select.
- Describir el modelo del select personalizable y qué piezas expone.
- Escribir un select estilado que degrade al control nativo sin romperse.
- Conocer el soporte exacto en agosto de 2026 y decidir si adoptarlo.
- Aplicar la regla que evita el error más caro del patrón.
El modelo: optar por un aspecto base, no apagar el dibujo
appearance: base-select no es appearance: none. La diferencia es conceptual y es la clave de todo. none significa “no pintes nada, me encargo yo” y te deja con una caja vacía y una lista de estados que reconstruir. base-select significa “cámbiame al aspecto base estilable”: un aspecto mínimo, definido en la especificación, igual en todos los motores, sobre el que puedes escribir CSS normal, y que conserva íntegro el comportamiento del control —el teclado, la búsqueda por escritura, la semántica de accesibilidad, el desplegado, el cierre al pulsar fuera—.
Hay que declararlo en dos sitios, y esa es la primera cosa que se olvida:
select, ::picker(select) {
appearance: base-select;
}
El primero cambia el botón. El segundo cambia el desplegable. Si solo declaras el primero, el botón se vuelve estilable y el menú sigue siendo el del sistema.
A partir de ahí, el desplegable es parte de tu página. Vive en la capa superior, igual que un dialog modal o un elemento con popover, con todo lo que eso implica: no le afecta overflow: hidden de ningún ancestro, no le afecta ningún contexto de apilamiento, y no hay z-index que valga porque está por encima de todo el árbol.
Las piezas que se exponen son:
| Pieza | Qué es |
|---|---|
::picker(select) |
el contenedor del desplegable |
::picker-icon |
la flecha del botón |
::checkmark |
la marca de la opción seleccionada |
selectedcontent |
el elemento que refleja dentro del botón el contenido de la opción activa |
:open |
la pseudo-clase que coincide mientras el desplegable está abierto |
Y lo que se desbloquea es lo que justifica todo el ejercicio: una option puede contener marcado. Un icono, una muestra de color, dos líneas de texto con jerarquía, una imagen. Hasta ahora el contenido de una option era texto plano por definición.
<select id="pais">
<button>
<selectedcontent></selectedcontent>
</button>
<option value="es">
<span class="bandera" aria-hidden="true">ES</span>
<span class="nombre">España</span>
</option>
<option value="pt">
<span class="bandera" aria-hidden="true">PT</span>
<span class="nombre">Portugal</span>
</option>
</select>
El <button> dentro del select es opcional y sirve para controlar la estructura interna del botón; <selectedcontent> clona ahí el contenido de la opción seleccionada, de modo que el icono aparece también cuando el menú está cerrado. Un navegador que no entienda nada de esto ignora el button, ignora selectedcontent y muestra el texto de las option como siempre.
select {
appearance: base-select;
border: 1px solid currentColor;
border-radius: 0.5em;
padding: 0.5em 0.75em;
}
::picker(select) {
appearance: base-select;
border: 1px solid color-mix(in oklch, currentColor 25%, transparent);
border-radius: 0.5em;
padding: 0.25em;
background: Canvas;
}
option {
display: flex;
gap: 0.6em;
align-items: center;
padding: 0.45em 0.6em;
border-radius: 0.35em;
}
option:checked { font-weight: 600; }
option:hover { background: color-mix(in oklch, currentColor 8%, transparent); }
select:open::picker-icon { rotate: 180deg; }
::picker-icon { transition: rotate 150ms ease; }
El soporte en agosto de 2026
Este es el punto donde hay que ser exacto, porque la función se ha divulgado mucho más de lo que se ha implementado.
| Motor | Estado en agosto de 2026 |
|---|---|
| Chrome y Edge | disponible desde Chrome 135, abril de 2025 |
| Safari | anunciado para Safari 27; en vista previa técnica, aún no en la versión estable |
| Firefox | en desarrollo, tras un flag |
Es decir: no es Baseline y no lo será durante un tiempo. Un motor entre cuatro lo tiene en estable. Eso no lo descarta —de hecho es un caso casi perfecto de mejora progresiva, porque el fallback es un select nativo perfectamente funcional— pero cambia por completo cómo se escribe.
La detección es limpia y hay que usarla:
@supports (appearance: base-select) {
select { appearance: base-select; /* ...el resto de tu diseño... */ }
::picker(select) { appearance: base-select; }
}
Envolver el bloque entero en @supports no es paranoia: evita que un motor que entienda ::picker(select) a medias aplique parte de tus reglas.
La regla de oro
Hay un error que anula todos los beneficios y que el equipo de WebKit ha llegado a documentar como la única regla imprescindible del patrón: cada option tiene que llevar contenido de texto o un texto accesible equivalente.
El error es tentador porque el diseño lo invita. Ahora que puedes meter marcado, sale natural poner solo un icono, o solo una muestra de color, y quitar el nombre porque “ya se entiende”. Eso rompe tres cosas a la vez:
- La usabilidad. Un icono sin etiqueta obliga a adivinar, y con el menú cerrado el botón muestra solo el icono, sin ninguna pista de qué hay seleccionado.
- La accesibilidad. Una
optionsin texto no le da nada al lector de pantalla ni a la línea braille ni al software de control por voz, que necesita un nombre pronunciable para poder decir “elige Portugal”. - La degradación. En los tres motores que todavía no soportan la función, y en cualquier situación donde el CSS no cargue —conexión lenta, proxy corporativo que filtra hojas de estilo, hoja de usuario—, el desplegable nativo muestra opciones vacías. No un diseño peor: opciones literalmente en blanco.
La formulación correcta es que el icono es una adición a la opción, nunca un sustituto. El texto puede ocultarse visualmente si el diseño lo pide, pero tiene que estar en el árbol de accesibilidad:
<option value="verde">
<span class="muestra" style="--c: oklch(0.7 0.16 150)" aria-hidden="true"></span>
<span class="visually-hidden">Verde bosque</span>
</option>
display: none y visibility: hidden sacan el contenido del árbol de accesibilidad; el texto deja de existir para todo el mundo. La clase de ocultación visual que sí funciona recorta el elemento a un píxel sin sacarlo del flujo: position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;. Es un patrón viejo y sigue siendo el correcto.
Qué hacer hoy
La decisión razonable en agosto de 2026 tiene tres escalones y ninguno exige comprometerse:
Si tu diseño se conforma con estilar el botón, no necesitas nada de esto. appearance: none sobre el select, tu flecha de fondo, y el desplegable nativo. Funciona en todas partes, es accesible por construcción y no tiene mantenimiento.
Si tu diseño necesita opciones con marcado, adopta base-select dentro de @supports, respeta la regla de oro y acepta que la mayoría de tus usuarios verán el desplegable nativo durante los próximos dos años. Cuando Safari 27 y Firefox lleguen, tu código ya estará listo y no habrá migración.
Si estás manteniendo un combobox de div, este es el momento de planificar su retirada. No de borrarlo mañana, pero sí de dejar de invertir en él y de dejar de crear componentes nuevos con ese patrón.
Merece la pena entender el argumento completo, porque no es “los div son malos”. Un combobox reconstruido con div puede ser correcto: existen implementaciones que lo son. El problema es de economía de mantenimiento. Un control nativo tiene una propiedad que ningún componente de biblioteca puede replicar: se actualiza sin ti. Cuando iOS cambió el desplegable por una rueda, todos los select nativos del mundo se adaptaron esa noche y ninguno de los componentes personalizados lo hizo. Cuando llegó el modo de contraste forzado de Windows, los nativos se mapearon solos. Cuando el control por voz aprendió a decir “toca el desplegable de país”, entendía los select reales. Cuando aparezcan modos de accesibilidad que aún no existen, los nativos los recibirán y tu componente de 2019 seguirá siendo de 2019. La cuenta que hay que hacer no es “¿puedo construir esto accesible hoy?” —la respuesta suele ser sí— sino “¿va a seguir siéndolo dentro de ocho años, en plataformas que todavía no existen, mantenido por gente que no soy yo?”, y ahí la respuesta empírica de dos décadas es que no. base-select es la plataforma reconociendo que la razón por la que la gente abandonó los controles nativos era legítima —eran inestilizables— y ofreciendo la única salida que preserva ambas cosas: dame el control del aspecto, quédate tú con el comportamiento. Es el mismo trato que ofrecen dialog, popover y el resto de los primitivos nuevos, y es el patrón de diseño más importante de la plataforma web en esta década.