Una guía para los selectores de pseudoclase de CSS modernos y recientemente admitidos

Publicado: 2022-03-10
Resumen rápido ↬ El borrador del editor del grupo de trabajo de CSS para selectores de nivel 4 incluye varios selectores de pseudoclase que ya tienen candidatos propuestos en la mayoría de los navegadores modernos. ¡Esta guía cubrirá los que actualmente tienen el mejor soporte junto con ejemplos para demostrar cómo puede comenzar a usarlos hoy!

Los selectores de pseudoclase son los que comienzan con el carácter de dos puntos “ : ” y coinciden en función del estado del elemento actual. El estado puede ser relativo al árbol del documento o en respuesta a un cambio de estado como :hover o :checked .

:any-link

Aunque se define en el nivel 4 de selectores, esta pseudoclase ha tenido compatibilidad entre navegadores durante bastante tiempo. La any-link coincidirá con un hipervínculo ancla siempre que tenga un href . Coincidirá de una manera equivalente a hacer coincidir tanto :link como :visited a la vez. Esencialmente, esto puede reducir sus estilos en un selector si está agregando propiedades básicas como el color que le gustaría aplicar a todos los enlaces, independientemente de su estado de visita.

 :any-link { color: blue; text-underline-offset: 0.05em; }

Una nota importante sobre la especificidad es que :any-link ganará contra a como selector incluso si a se coloca más abajo en la cascada ya que tiene la especificidad de una clase. En el siguiente ejemplo, los enlaces serán de color púrpura:

 :any-link { color: purple; } a { color: red; }

Entonces, si introduce :any-link , tenga en cuenta que deberá incluirlo en las instancias de a como selector si estarán en competencia directa por la especificidad.

:focus-visible

Apuesto a que una de las infracciones de accesibilidad más comunes en la web es eliminar el outline de elementos interactivos como enlaces, botones y entradas de formulario para su :focus . Uno de los propósitos principales de ese outline es servir como un indicador visual para los usuarios que utilizan principalmente teclados para navegar. Un estado de enfoque visible es fundamental como una herramienta de orientación a medida que los usuarios navegan por una interfaz y para ayudar a reforzar lo que es un elemento interactivo. Específicamente, el foco visible está cubierto en el Criterio de Conformidad 2.4.11 de las WCAG: Aspecto del foco (mínimo).

La :focus-visible está diseñada para mostrar solo un anillo de enfoque cuando el agente de usuario determina a través de la heurística que debe ser visible. Dicho de otra manera: los navegadores determinarán cuándo aplicar :focus-visible en función de elementos como el método de entrada, el tipo de elemento y el contexto de la interacción. Para fines de prueba a través de una computadora de escritorio con entrada de teclado y mouse, debería ver :focus-visible adjuntos cuando ingresa a un elemento interactivo, pero no cuando hace clic en él, con la excepción de las entradas de texto y las áreas de texto que deben mostrar :focus-visible para todos los tipos de entrada de enfoque.

Nota : Para obtener más detalles, revise el borrador de trabajo de la especificación :focus-visible .

Las últimas versiones de los navegadores Firefox y Chromium ahora parecen manejar :focus-visible en las entradas de formulario de acuerdo con la especificación que dice que la UA debe eliminar :focus estilos cuando :focus-visible coincide. Safari aún no es compatible con :focus-visible , por lo que debemos asegurarnos de que se incluya un estilo de :focus como respaldo para evitar eliminar el outline para accesibilidad.

¡Más después del salto! Continúe leyendo a continuación ↓

Dado un botón y una entrada de texto con el siguiente conjunto de estilos, veamos qué sucede:

 input:focus, button:focus { outline: 2px solid blue; outline-offset: 0.25em; } input:focus-visible { outline: 2px solid transparent; border-color: blue; } button:focus:not(:focus-visible) { outline: none; } button:focus-visible { outline: 2px solid transparent; box-shadow: 0 0 0 2px #fff, 0 0 0 4px blue; }

cromo y firefox

  • input
    Elimine correctamente :focus cuando los elementos se enfocan a través de la entrada del mouse a favor de :focus-visible lo que resulta en cambiar el border-color y ocultar el outline en la entrada del teclado
  • button
    No solo usa :focus-visible sin la regla adicional para button:focus:not(:focus-visible) que elimina el contorno en :focus , pero permitirá la visibilidad de la box-shadow solo en la entrada del teclado

Safari

  • input
    Continúa usando solo los estilos :focus
  • button
    Esto parece ahora respetar parcialmente la intención de :focus-visible en el botón al ocultar los :focus styles al hacer clic, pero aún muestra los :focus styles en la interacción del teclado

Entonces, por ahora, la recomendación sería continuar incluyendo :focus y luego mejorar progresivamente hasta usar :focus-visible que permite el código de demostración. Aquí hay un CodePen para que continúe probando con:

Consulte el Pen [Aplicación de prueba de :focus-visible](https://codepen.io/smashingmag/pen/MWJZbew) de Stephanie Eckles.

Consulte la aplicación Pen Testing de :focus-visible de Stephanie Eckles.

:focus-within

La :focus-within es compatible con todos los navegadores modernos y actúa casi como un selector principal, pero solo para una condición muy específica. Cuando se adjunta a un elemento contenedor y un elemento secundario coincide con :focus , los estilos se pueden agregar al elemento contenedor y/o a cualquier otro elemento dentro del contenedor.

Una mejora práctica para usar este comportamiento es diseñar una etiqueta de formulario cuando la entrada asociada tiene el foco. Para que esto funcione, envolvemos la etiqueta y la entrada en un contenedor, y luego adjuntamos :focus-within a ese contenedor y seleccionamos la etiqueta:

 .form-group:focus-within label { color: blue; }

Esto da como resultado que la etiqueta se vuelva azul cuando la entrada tiene foco.

Esta demostración de CodePen también incluye agregar un esquema directamente al contenedor .form-group :

Consulte el Pen [Aplicación de prueba de :focus-within](https://codepen.io/smashingmag/pen/xxgmREq) de Stephanie Eckles.

Vea la aplicación Pen Testing de :focus-within de Stephanie Eckles.

:is()

También conocida como la pseudoclase "coincide con cualquier", :is() puede tomar una lista de selectores para intentar compararlos. Por ejemplo, en lugar de enumerar los estilos de encabezado individualmente, puede agruparlos bajo el selector de :is(h1, h2, h3) .

Un par de comportamientos únicos sobre la lista de selectores :is() :

  • Si un selector de la lista no es válido, la regla seguirá coincidiendo con los selectores válidos. Dado :is(-ua-invalid, article, p) la regla coincidirá con article y p .
  • La especificidad calculada será igual a la del selector pasado con la especificidad más alta. Por ejemplo, :is(#id, p) tendrá la especificidad de #id — 1.0.0 — mientras que :is(p, a) tendrá una especificidad de 0.0.1.

El primer comportamiento de ignorar los selectores no válidos es un beneficio clave. Al usar otros selectores en un grupo donde un selector no es válido, el navegador descartará toda la regla. Esto entra en juego en algunos casos en los que los prefijos del proveedor aún son necesarios, y la agrupación de selectores con prefijo y sin prefijo hace que la regla falle en todos los navegadores. Con :is() puede agrupar con seguridad esos estilos y se aplicarán cuando coincidan y se ignorarán cuando no coincidan.

Para mí, agrupar estilos de encabezado como se mencionó anteriormente ya es una gran victoria con este selector. También es el tipo de regla con el que me sentiría cómodo usando sin respaldo al aplicar estilos no críticos, como:

 :is(h1, h2, h3) { line-height: 1.2; } :is(h2, h3):not(:first-child) { margin-top: 2em; }

En este ejemplo (que proviene de los estilos de documento en mi proyecto SmolCSS), tener la mayor line-height heredada de los estilos base o la falta del margin-top no es realmente un problema para los navegadores que no son compatibles. Es simplemente menos que ideal. Lo que no querría usar :is() todavía serían estilos de diseño críticos como Grid o Flex que controlan significativamente su interfaz.

Además, cuando se encadena a otro selector, puede probar si el selector base coincide con un selector descendiente dentro de :is() . Por ejemplo, la siguiente regla selecciona solo párrafos que son descendientes directos de artículos. El selector universal se utiliza como referencia al selector de base p .

 p:is(article > *)

Para obtener el mejor soporte actual, si desea comenzar a usarlo, también querrá duplicar los estilos al incluir reglas duplicadas usando :-webkit-any() y :matches() . ¡Recuerde hacer estas reglas individuales, o incluso el navegador compatible lo eliminará! En otras palabras, incluya todo lo siguiente:

 :matches(h1, h2, h3) { } :-webkit-any(h1, h2, h3) { } :is(h1, h2, h3) { }

Vale la pena mencionar en este punto que, junto con los selectores más nuevos, hay una variación actualizada de @supports , que es el @supports selector . Esto también está disponible como @supports not selector .

Nota : en la actualidad (de los navegadores modernos), solo Safari no admite esta regla.

Podría verificar la compatibilidad con :is() con algo como lo siguiente, pero en realidad perdería la compatibilidad con Safari, ya que Safari admite :is() pero no admite @supports selector .

 @supports selector(:is(h1)) { :is(h1, h2, h3) { line-height: 1.1; } }

:where()

La pseudoclase :where() es casi idéntica a :is() excepto por una diferencia crítica: siempre tendrá cero especificidad. Esto tiene implicaciones increíbles para las personas que están construyendo marcos, temas y sistemas de diseño . Usando :where() , un autor puede establecer valores predeterminados y los desarrolladores posteriores pueden incluir anulaciones o extensiones sin conflictos de especificidad.

Considere el siguiente conjunto de estilos img . Usando :where() , incluso con un selector de mayor especificidad, la especificidad sigue siendo cero. En el siguiente ejemplo, ¿qué borde de color crees que tendrá la imagen?

 :where(article img:not(:first-child)) { border: 5px solid red; } :where(article) img { border: 5px solid green; } img { border: 5px solid orange; }

La primera regla tiene cero especificidad ya que está completamente contenida dentro de :where() . Entonces, directamente contra la segunda regla, la segunda regla gana. Al presentar el selector de solo elemento img como la última regla, ganará debido a la cascada. Esto se debe a que calculará con la misma especificidad que la regla :where(article) img ya que la porción :where() no aumenta la especificidad.

Usar :where() junto con las alternativas es un poco más difícil debido a la función de especificidad cero, ya que es probable que esa función sea la razón por la que querría usarla sobre :is() . Y si agrega reglas de respaldo, es probable que superen a :where() debido a su propia naturaleza. Y tiene un mejor soporte general que el @supports selector por lo que no es probable que tratar de usarlo para crear una alternativa proporcione mucha ganancia (si la hay). Básicamente, tenga en cuenta la incapacidad de crear correctamente respaldos para :where() y verifique cuidadosamente sus propios datos para determinar si es seguro comenzar a usarlos para su audiencia única.

Puede seguir probando :where() con el siguiente CodePen que usa los selectores img de arriba:

Consulte el Pen [Prueba de la especificidad `:where()`](https://codepen.io/smashingmag/pen/jOyXVMg) de Stephanie Eckles.

Consulte la especificidad de Pen Testing :where() de Stephanie Eckles.

Mejorado :not()

El selector base :not() ha sido compatible desde Internet Explorer 9. Pero el nivel 4 de selectores mejora :not() al permitirle tomar una lista de selectores, al igual que :is() y :where() .

Las siguientes reglas proporcionan el mismo resultado en navegadores compatibles:

 article :not(h2):not(h3):not(h4) { margin-bottom: 1.5em; } article :not(h2, h3, h4) { margin-bottom: 1.5em; }

La capacidad de :not() para aceptar una lista de selectores tiene una gran compatibilidad con los navegadores modernos.

Como vimos con :is() , Enhanced :not() también puede contener una referencia al selector base como descendiente usando * . Este CodePen demuestra esta capacidad al seleccionar enlaces que no son descendientes de nav .

Vea el Pen [Pruebas: no () con un selector descendente] (https://codepen.io/smashingmag/pen/BapvQQv) de Stephanie Eckles.

Consulte Pruebas de penetración: no () con un selector descendiente de Stephanie Eckles.

Bonificación : la demostración anterior también incluye un ejemplo de encadenamiento de :not() y :is() para seleccionar imágenes que no son hermanos adyacentes de los elementos h2 o h3 .

Propuesto pero “en riesgo” — :has()

La pseudo-clase final que es una propuesta muy emocionante pero que no tiene ningún navegador actual que la implemente, incluso de forma experimental, es :has() . De hecho, aparece en el Borrador del Editor del Nivel 4 del Selector como “en riesgo”, lo que significa que se reconoce que tiene dificultades para completar su implementación y, por lo tanto, puede eliminarse de la recomendación.

Si se implementa, :has() sería esencialmente el "selector principal" que muchas personas de CSS anhelaban tener disponible. Funcionaría con una lógica similar a una combinación de :focus-within y :is() con selectores de descendientes, donde está buscando la presencia de descendientes pero el estilo aplicado sería para el elemento principal.

Dada la siguiente regla, si la navegación contenía un botón, entonces la navegación habría disminuido el relleno superior e inferior:

 nav { padding: 0.75rem 0.25rem; nav:has(button) { padding-top: 0.25rem; padding-bottom: 0.25rem; }

Una vez más, esto no está implementado actualmente en ningún navegador, ni siquiera de forma experimental, ¡pero es divertido pensar en ello! Robin Rendle proporcionó información adicional sobre este futuro selector en CSS-Tricks.

Mención de Honor del Nivel 3: :empty

Una pseudoclase útil que puede haber pasado por alto del nivel 3 de selectores es :empty , que coincide con un elemento cuando no tiene elementos secundarios, incluidos los nodos de texto.

La regla p:empty coincidirá con <p></p> pero no con <p>Hello</p> .

Una forma de usar :empty es ocultar elementos que quizás sean marcadores de posición para contenido dinámico que se completa con JavaScript. Tal vez tenga un div que recibirá los resultados de la búsqueda y, cuando se complete, tendrá un borde y algo de relleno. Pero sin resultados todavía, no querrás que ocupe espacio en la página. Usando :empty puedes ocultarlo con:

 .search-results:empty { display: none; }

Es posible que esté pensando en agregar un mensaje en estado vacío y tenga la tentación de agregarlo con un pseudoelemento y content . El escollo aquí es que los mensajes pueden no estar disponibles para los usuarios de tecnología de asistencia que no son consistentes en cuanto a si pueden acceder al content . En otras palabras, para asegurarse de que un tipo de mensaje "sin resultados" sea accesible , querrá agregarlo como un elemento real como un párrafo (una aria-label ya no sería accesible para un div oculto).

Recursos para aprender acerca de los selectores

CSS tiene muchos más selectores que incluyen pseudoclases. Aquí hay algunos lugares más para obtener más información sobre lo que está disponible:

  • La documentación de los selectores CSS de MDN incluye una lista categorizada completa;
  • He escrito una guía de dos partes para selectores de CSS avanzados, puede comenzar con la primera parte;
  • Diviértete aprendiendo sobre selectores CSS con el juego CSS Diner;
  • Kitty Giraudel creó una herramienta de explicación del selector que desglosará y describirá partes de un selector proporcionado.