wandres.dev
MEDIA QUERIES MODERNAS · Rangos y consultas de usuario

Consultas de puntero: hover none no significa móvil

hover, any-hover, pointer y any-pointer: qué mide cada una, por qué la equivalencia con el tamaño de pantalla rompe interfaces, y los cuatro dispositivos que la desmienten.

⏱ 17 min

Las cuatro consultas de puntero son la única forma que tienes de saber algo sobre cómo el usuario interactúa con tu interfaz, y también la fuente de una de las suposiciones más dañinas del desarrollo web: que hover: none equivale a móvil y hover: hover a escritorio. Esa equivalencia era aproximadamente cierta en 2014 y hoy falla en un portátil táctil, en un móvil con ratón, en una tableta con teclado y en cualquier Windows en modo tableta. Y cuando falla, lo que se rompe suele ser el acceso a una función, no la estética.

🎯 Al terminar esta lección sabrás
  • Distinguir hover de any-hover y pointer de any-pointer con precisión.
  • Enumerar dispositivos reales donde la equivalencia con el tamaño de pantalla se rompe.
  • Elegir la consulta correcta para agrandar objetivos táctiles y para revelar acciones al pasar.
  • Escribir mejoras basadas en puntero que nunca dejen una función inalcanzable.

Las cuatro consultas

Hay dos ejes. Uno es qué se pregunta: si el dispositivo puede pasar por encima sin activar (hover) o cuál es la precisión del apuntado (pointer). El otro es sobre qué dispositivo: el primario, o cualquiera de los disponibles.

Consulta Valores Sobre qué
hover none, hover el mecanismo de entrada principal
any-hover none, hover cualquiera de los disponibles
pointer none, coarse, fine el mecanismo principal
any-pointer none, coarse, fine cualquiera de los disponibles

coarse significa un apuntado impreciso, típicamente un dedo, cuya área efectiva ronda los 40px. fine significa un apuntado preciso: ratón, trackpad, lápiz. none significa que no hay dispositivo de apuntado en absoluto: un mando de televisión con teclas direccionales, una interfaz manejada solo con teclado.

Las versiones any-* pueden devolver varios valores a la vez conceptualmente: en un portátil táctil, any-pointer: fine y any-pointer: coarse son las dos verdaderas, porque hay ratón y hay pantalla táctil.

Los cuatro dispositivos que rompen la equivalencia

El portátil con pantalla táctil. Su puntero principal es el trackpad, así que hover: hover y pointer: fine. Pero el usuario puede tocar la pantalla, y any-pointer: coarse también es cierto. Si has dimensionado los objetivos táctiles solo bajo pointer: coarse, este usuario obtiene botones de ratón que tiene que acertar con el dedo.

El móvil o la tableta con ratón o trackpad conectado. iPadOS y Android admiten ratón desde hace años, y buena parte del trabajo en tableta se hace con teclado y trackpad. En ese modo el dispositivo reporta hover: hover y pointer: fine, y una interfaz que asumió “pantalla pequeña, luego dedo” le da controles diminutos… o al revés, una que asumió “táctil, luego sin hover” le esconde acciones que sí podría descubrir.

Windows en modo tableta y los dispositivos convertibles. El mecanismo principal cambia durante la sesión, al girar la pantalla o desconectar el teclado. Las media queries reevalúan, así que tu CSS reacciona; el código JavaScript que leyó matchMedia una vez al cargar, no.

El lápiz. Un stylus es pointer: fine porque apunta con precisión, y en muchos dispositivos hover: none porque no hay estado de proximidad. Es la combinación que más suposiciones rompe, porque no encaja en ninguna de las dos categorías mentales habituales.

🛑
El fallo con consecuencias reales

El patrón dañino es este: un menú de acciones que solo aparece al pasar por encima, envuelto en @media (hover: hover), sin ninguna alternativa. En un dispositivo con hover: none esas acciones no existen: no hay forma de llegar a ellas ni con el dedo ni con el teclado. Es un fallo de funcionalidad, no de diseño, y no aparece en ninguna captura de pantalla ni en ninguna prueba automática de las habituales.

Cómo usarlas bien

Para agrandar objetivos táctiles, usa any-pointer: coarse. Es la consulta que atrapa también a los híbridos. La versión con pointer: coarse deja fuera al portátil táctil, que es exactamente el caso donde el usuario alterna.

.accion { min-block-size: 2rem; padding-inline: 0.5rem; }

@media (any-pointer: coarse) {
  .accion { min-block-size: 2.75rem; padding-inline: 0.875rem; }
}

Para efectos de pasar por encima, usa hover: hover. Aquí sí interesa el dispositivo principal, porque lo que quieres evitar es el “hover pegajoso” de las pantallas táctiles: al tocar un elemento con estado :hover, muchos navegadores lo dejan aplicado hasta que tocas otra cosa, y el resultado son tarjetas que se quedan iluminadas.

@media (hover: hover) {
  .tarjeta:hover { transform: translateY(-2px); }
  .tarjeta:hover .acciones { opacity: 1; }
}

Nunca escondas funcionalidad detrás de hover. Si las acciones solo se ven al pasar por encima en escritorio, tienen que estar visibles siempre en el resto, o accesibles con otro gesto. La forma robusta es hacer que el estado base sea el visible y que el ocultarlas sea la mejora:

.acciones { opacity: 1; }                 /* visibles por defecto: seguro */

@media (hover: hover) {
  .acciones { opacity: 0; transition: opacity 150ms; }
  .tarjeta:hover .acciones,
  .tarjeta:focus-within .acciones { opacity: 1; }   /* el teclado, tambien */
}

Ese :focus-within no es opcional: sin él, quien navega con teclado en un dispositivo con ratón no puede ver las acciones aunque pueda enfocarlas, lo que produce el peor de los casos posibles.

Lo que estas consultas no te dicen

No te dicen el tamaño de la pantalla. No te dicen el sistema operativo. No te dicen si es un teléfono. No te dicen si hay teclado. Y no te dicen qué dispositivo está usando el usuario ahora mismo: te dicen qué hay disponible, y en un híbrido puede estar alternando cada minuto.

Merece la pena mencionar una consulta emparentada y poco usada: update, con valores none, slow y fast, que indica con qué frecuencia puede el dispositivo repintar. update: slow es el caso de las pantallas de tinta electrónica, donde cualquier animación produce fantasmas y conviene desactivarla:

@media (update: slow) {
  * { animation: none !important; transition: none !important; }
}
Toda inferencia sobre el usuario a partir de una señal técnica caduca

La historia de las suposiciones sobre dispositivos en la web es una sucesión de correlaciones que fueron útiles hasta que dejaron de serlo, y siempre por el mismo mecanismo: la señal medía algo real —el ancho de pantalla, la cadena de agente de usuario, la capacidad de hover— y se usó como proxy de algo distinto que era lo que realmente se quería saber. Pantalla estrecha se usó como proxy de “móvil”, que se usó como proxy de “dedo”, que se usó como proxy de “conexión lenta y prisa”. Cada eslabón de esa cadena era una correlación estadística de su momento, y cada uno se rompió por separado en cuanto el mercado produjo un dispositivo que no encajaba. La lección no es “usa any-pointer en vez de pointer”, que también: es que cuando uses una señal como proxy de otra cosa, escribas cuál es la cosa. Si lo que quieres es que los objetivos sean grandes para los dedos, la señal correcta es la existencia de un puntero grueso, no el ancho. Si lo que quieres es no gastar datos, la señal correcta no es “es móvil”. El ejercicio es de dos segundos y es el mismo en cualquier sistema que tome decisiones sobre usuarios: nombra la propiedad que de verdad te importa antes de elegir cómo medirla, porque en cuanto la medida se convierte en la definición, has dejado de poder corregirla.

⚔️ Audita tus suposiciones de dispositivo
  1. Busca en tu base de código todos los @media (hover: hover) y comprueba que ninguno esconde una función sin alternativa.
  2. Cambia los pointer: coarse que dimensionen objetivos táctiles por any-pointer: coarse y razona el efecto en un portátil táctil.
  3. Añade :focus-within a cada regla que revele contenido al pasar por encima.
  4. Emula un dispositivo táctil en las herramientas de desarrollo y recorre tu interfaz con el tabulador.
  5. Busca cualquier matchMedia de puntero leído una sola vez al cargar y conviértelo en un listener de cambios.