Forzar estados: inspeccionar lo que solo existe mientras no miras
Las pseudo-clases que el panel puede forzar, cómo se aplican por debajo, y las cuatro técnicas para inspeccionar elementos que desaparecen en cuanto mueves el ratón.
Los estados de interacción tienen una propiedad incómoda para el que quiere inspeccionarlos: existen mientras el usuario hace algo, y mover el ratón hacia el panel es dejar de hacerlo. El menú se cierra, el tooltip desaparece, el foco se va. Forzar estados resuelve la mitad de esos casos, congelar la ejecución resuelve la otra mitad, y saber cuál usar depende de si el estado lo produce el CSS o lo produce JavaScript.
- Forzar las pseudo-clases de estado desde el panel y entender qué hace el navegador al hacerlo.
- Distinguir un estado producido por CSS de uno producido por JavaScript y elegir la técnica adecuada.
- Inspeccionar menús, tooltips y popovers que se cierran al perder el foco o el hover.
- Emular las preferencias del usuario que cambian qué reglas casan.
Qué pseudo-clases se pueden forzar y qué significa forzarlas
En la cabecera del panel de estilos hay un control que despliega una lista de casillas con los estados forzables. Los habituales son :active, :hover, :focus, :focus-within, :focus-visible y :target.
Marcar una casilla no simula el evento: le dice al motor que, a efectos de emparejamiento de selectores, considere que ese elemento tiene esa bandera de estado activa. La consecuencia es precisa y merece entenderse: cambia qué reglas casan, y nada más.
De ahí se derivan tres cosas que sorprenden la primera vez.
Forzar :hover sí dispara transiciones, porque las transiciones se disparan cuando cambia el valor computado de una propiedad, y forzar el estado cambia esos valores. Es la forma de ver una transición de hover en cámara lenta si además la ralentizas.
Forzar :focus no mueve el foco real del documento. document.activeElement sigue siendo lo que fuera. Así que si tu código JavaScript reacciona al foco real, forzar la pseudo-clase no lo despertará. Para eso hay que enfocar de verdad, con $0.focus() desde la consola.
Forzar :active no dispara ningún manejador de clic. Solo cambia el aspecto.
El estado forzado se marca en el árbol con un punto naranja junto al nodo, y persiste hasta que lo quitas o recargas. Se puede tener forzado en varios nodos a la vez, lo cual es cómodo y también una fuente de confusión si te olvidas de uno.
Los estados forzados se aplican a un nodo concreto, así que para ver el hover de un elemento que además tiene reglas dependientes del hover del padre —el patrón clásico de un menú que se despliega con .menu:hover .submenu— hay que forzar el estado en el padre, no en el submenú. Este es el error número uno con esta función.
Emular las preferencias del usuario
Hay un segundo grupo de estados que no son del elemento sino del entorno, y que también cambian qué reglas casan. Se controlan desde la pestaña de renderizado del cajón inferior, accesible por el menú de comandos escribiendo rendering.
prefers-color-scheme alterna entre claro y oscuro, y es la forma correcta de probar el modo oscuro sin cambiar el sistema entero.
prefers-reduced-motion activa la preferencia de movimiento reducido, que es la que debería desactivar tus animaciones y que casi nadie prueba nunca.
prefers-contrast y prefers-reduced-transparency cubren las otras dos preferencias de presentación.
forced-colors emula el modo de colores forzados de Windows, donde el sistema sustituye tu paleta entera. Es una de las emulaciones más valiosas porque rompe muchísimas interfaces que nadie ha probado nunca en ese modo, y porque el fallo es total: los iconos dibujados con fondos desaparecen, los bordes de un píxel se comen la interfaz, y el texto sobre imagen se vuelve ilegible.
Además está la emulación de print, que es la única forma cómoda de depurar una hoja de estilos de impresión sin abrir la vista previa cada vez.
Todas estas emulaciones son intervenciones con persistencia hasta que cierras el panel, así que entran en la lista de cosas que se quedan puestas y luego desconciertan.
Los elementos que huyen
Cuatro técnicas, en orden de cuándo aplicarlas.
Si el estado lo produce CSS puro, fuerza la pseudo-clase en el ancestro correcto. Cubre menús desplegables con hover, tooltips con :hover y estados de foco.
Si el estado lo produce JavaScript, forzar la pseudo-clase no sirve de nada, porque el submenú simplemente no está en el DOM o tiene una clase que el script quita. La técnica es congelar la ejecución con retardo desde la consola.
setTimeout(() => { debugger; }, 5000);
Ejecutas eso, provocas el estado con el ratón o el teclado, y a los cinco segundos el hilo principal se detiene con el DOM exactamente como está. Puedes inspeccionar todo lo que quieras: el árbol está congelado y no reacciona. Al continuar la ejecución, sigue donde estaba.
Si el elemento se cierra al perder el foco, hay una variante más limpia que no congela nada: desactivar el manejador que lo cierra. Con getEventListeners($0) averiguas qué listener hay registrado, y con monitorEvents verificas qué evento lo dispara. A menudo basta con saber que el culpable es un blur o un focusout sobre document.
Si nada de lo anterior aplica, queda la vía bruta: encontrar el nodo por búsqueda en el árbol mientras el estado está activo, usando el teclado y sin tocar el ratón. Si el menú se abre con la tecla de entrada y se cierra con el ratón, el teclado es tu amigo.
De todas las intervenciones de las DevTools, el estado forzado es la que más daño hace por lo silenciosa que es. La señal es un punto pequeño junto al nodo en el árbol, en un panel donde ya hay decenas de marcas, y sobrevive a que cambies de nodo, de panel e incluso de pestaña dentro de las DevTools. El escenario que se repite: fuerzas :hover en un botón para mirar su color de fondo, te distraes con otra cosa, y veinte minutos después estás intentando entender por qué ese botón se ve resaltado cuando no debería, o —mucho peor— por qué una transición no se dispara al pasar el ratón. No se dispara porque el estado ya estaba activo antes de que el ratón llegara, así que no hay cambio de valor y por tanto no hay transición. Ese caso concreto ha generado informes de bugs contra librerías de componentes que no tenían ningún bug. Hay dos hábitos que lo evitan. El primero es buscar el punto naranja en el árbol siempre que un elemento se comporte de forma inexplicable en relación con la interacción; el propio menú de estados forzados muestra cuáles están activos en el nodo seleccionado. El segundo, más general y que vale para toda intervención: recargar antes de creerse cualquier comportamiento interactivo, porque el estado forzado no sobrevive a la recarga y por tanto una recarga es un test decisivo de un segundo. La regla se generaliza: cuando algo se comporta de una forma que contradice el código, la primera hipótesis no debería ser un bug del navegador sino una intervención tuya que se te olvidó.
Coge cualquier página con un menú desplegable que se abra al pasar el ratón, y consigue inspeccionar los estilos del submenú de tres formas distintas: forzando el estado en el ancestro, congelando con setTimeout y debugger, y desde la consola con $$('.submenu') para llegar al nodo sin interacción. Comparar las tres enseña más sobre cómo está construido ese menú que leer su código.