CSS Selectores de hermanos anteriores y cómo falsificarlos

Si alguna vez usó selectores de hermanos CSS, sabe que solo hay dos. El combinador de hermanos + selecciona la primera coincidencia que viene inmediatamente después, y el combinador de hermanos subsiguientes coincide con todos los que vienen después.
Pero no hay forma de seleccionar lo que vino antes. Los selectores primarios o los selectores de hermanos anteriores simplemente no son una cosa.

Sé que lo quieres, sabes que lo quiero, pero la cruda verdad es que no existen (y probablemente nunca lo harán). Hay un millón de publicaciones sobre los porqués. Incluso hay propuestas sobre cómo implementarlos. Pero estamos atrapados en el procesamiento unidireccional de las reglas CSS, lo más probable es que nos protejan de nuestra "falta de experiencia" que nos atasca en los flujos de reflujo e incluso bucles infinitos.

Afortunadamente, como con la mayoría de las limitaciones de CSS, podemos fingirlo.

Lo primero a considerar es por qué queremos que los hermanos anteriores comiencen.
Me vienen a la mente dos casos:

  1. Necesitamos seleccionar a todos los hermanos de un determinado elemento, y el combinador de hermanos posterior solo selecciona los que vienen después.
  2. Necesitamos seleccionar solo hermanos que vinieron antes

1. Seleccionar todos los hermanos

A veces necesitamos seleccionar los hermanos anteriores y siguientes. Para hacer eso, podemos seleccionar al padre y usar algunos trucos a su alrededor.

Por ejemplo, para seleccionar todos los tramos en la siguiente estructura cuando colocamos el cursor sobre cualquiera de ellos, podríamos usar el selector secundario en el desplazamiento del elemento primario. Nos aseguramos de deshabilitar los eventos de puntero del padre y restablecerlo en los hijos. Entonces, cualquier acción que queramos que suceda solo se activará cuando ingresemos al niño y no al padre en sí.

Si necesita seleccionar todos los hermanos, excepto el que está siendo suspendido, puede combinar la técnica anterior con el selector: no para excluirlo.

Un caso de uso típico para esto son los menús:

El código anterior rechazará la opacidad de todos los elementos

  • excepto el que se está suspendiendo.

    Además, puede usar filtros como selectores de tipo y enésimo para ser más precisos en los hermanos que desea afectar.

    Con un poco de estilo, debería funcionar así:

    Tenga en cuenta: si va a ejecutar los eventos de puntero: ninguno se acerca, tenga en cuenta que puede interferir con el apilamiento (podría permitirle seleccionar elementos que están "debajo" en el orden de apilamiento). Tampoco funcionará en IE10 y versiones posteriores, aparte de la implicación de que podría necesitar los eventos de puntero para otra cosa. Así que tenga mucho cuidado al usarlo.

    2. Seleccionar lo que vino antes

    Para este caso de uso, podemos revertir el orden en el HTML, luego ordenarlo nuevamente en CSS y usar el ~ combinador de hermanos posterior o el selector de hermanos adyacentes. De esta manera, seleccionaremos a los próximos hermanos, pero parecerá que estamos seleccionando a los anteriores.

    Hay varias formas de hacer esto. Lo más simple y probablemente lo más antiguo es cambiar la dirección de escritura de nuestro contenedor:

    Si sus elementos necesitan mostrar texto real, siempre puede revertirlo:

    Pero eso puede salirse de control de muchas maneras. Afortunadamente, la moderna caja de herramientas CSS lo hace mucho más simple y seguro. Podemos usar Flexbox en el contenedor e invertir el orden con flex-direction: row-reverse:

    Lo mejor del enfoque de Flexbox es que no nos metemos con la dirección de escritura. No necesitamos restablecer a los niños, y todo es mucho más predecible.

    Uso de "hermanos anteriores" para crear un sistema de calificación de estrellas solo CSS

    Semánticamente, un sistema de calificación puede considerarse como una simple lista de botones de radio con sus etiquetas correspondientes. Eso es útil, ya que nos permitirá usar el pseudo-selector marcado para modificar los hermanos.

    Entonces, comencemos desde allí:

    Como discutimos anteriormente, los elementos están en orden inverso para permitir un selector de "hermano anterior". Observe que estamos usando el carácter de "estrella blanca" unicode (U + 2606) para representar las estrellas vacías.

    Vamos a mostrarlos uno al lado del otro, en el orden correcto (inverso):

    Ahora esconda los botones de radio, nadie quiere ver eso:

    Y aplique un poco de estilo a los personajes estrella:

    La única línea verdaderamente importante es la posición: relativa. Nos permitirá colocar en posición absoluta un pseudo elemento de estrella llena (U + 2605) encima, que se ocultará inicialmente.

    Cuando pasamos el cursor sobre una estrella, el pseudo elemento de estrella llena debería ser visible para ella y para todos los hermanos anteriores.

    Lo mismo para la calificación seleccionada, haciendo coincidir todas las etiquetas que vienen antes del botón de radio marcado:

    Recuerde que usar la bandera! Important es exactamente lo contrario de una buena práctica. Lo hago aquí, ya que no hay otra forma de lograr la funcionalidad adicional que se analiza en la siguiente sección sin ella.

    Por último, pero no menos importante, debemos "recordar" la calificación actual, en caso de que el usuario quiera cambiarla. Por ejemplo, si hubieran seleccionado cinco estrellas, y por alguna razón quisieran cambiarlo a cuatro, deberíamos mostrar las estrellas 1 a 4 como llenas y la quinta como semitransparente al pasar sobre la cuarta.

    Eso se puede lograr cambiando la opacidad de los hermanos anteriores de la entrada marcada al pasar el mouse sobre el contenedor:

    Por eso también necesitábamos la opacidad: 1! Importante en la declaración inicial de desplazamiento. De lo contrario, esta última regla habría ganado el concurso de especificidad y aplicado un relleno semitransparente a todo.

    Y ahí lo tenemos, un sistema de clasificación de estrellas entre navegadores, completamente funcional, solo CSS, que utiliza selectores de "hermanos anteriores".

    Como puede ver, solo porque "es imposible" no significa que no deba intentarlo. La programación se trata de superar los límites. Entonces, cada vez que golpeas la pared, solo empuja un poco más fuerte. ¿O supongo que encontrar el camino podría ser una mejor analogía? ... de todos modos, ya sabes a lo que me refiero. ¡Sigue hackeando!

    Una nota sobre accesibilidad

    El fragmento anterior es una simplificación para facilitar su comprensión. No es algo que recomendaría usar en producción debido a muchas limitaciones de accesibilidad.

    Para hacer que el fragmento sea un poco más accesible, lo primero sería ocultar los botones de radio con prácticamente cualquier otra técnica que no sea la pantalla: ninguna para hacerlos enfocables. También deberíamos agregar un anillo de enfoque en todo el fragmento de estrellas cuando cualquier elemento en el interior esté enfocado, a través del pseudo-selector: focus-inside.

    Las etiquetas idénticas "☆" no tienen sentido para los lectores de pantalla, por lo que el mejor enfoque será tener un dentro de la etiqueta con el texto "n Estrellas", que estará oculto para los usuarios videntes.

    También la fuente HTML inversa + pantalla: el enfoque de reversa en fila hace que la calificación del teclado sea incómoda, ya que no se revierte. La accesibilidad de Flexbox y el teclado es un tema bastante complicado, pero lo más parecido a una solución para eso es agregar aria-flowtotag a cada elemento, que al menos soluciona el problema para algunos lectores de pantalla + combinaciones de navegador.

    Para obtener un fragmento más accesible (utilizando una técnica alternativa de modificación de los próximos hermanos para que parezcan vacíos en lugar de tratar de evaluar a los anteriores), consulte Patrick Cole, como lo discutimos en las respuestas a continuación.