Apuntes sobre Html2

Cuando uno enseña, dos aprenden.

Selectores avanzados

Combinadores

Los combinadores describen la relación entre dos selectores: no apuntan a un elemento aislado, sino a uno en función de su posición respecto a otro en el árbol del documento. Son cuatro: espacio, >, + y ~.

Descendiente (espacio)

El espacio entre dos selectores significa "el segundo dentro del primero, a cualquier profundidad":

/* Cualquier <a> que esté dentro de .navegacion, sea a cualquier nivel */
.navegacion a {
    text-decoration: none;
    color: inherit;
}

/* Cualquier <strong> dentro de un <li> */
li strong {
    color: #84ba3f;
}
<nav class="navegacion">
    <ul>
        <li><a href="/">Inicio</a></li>     <!-- afectado -->
        <li><a href="/blog">Blog</a></li>  <!-- afectado -->
    </ul>
</nav>
<a href="/otro">Fuera del nav</a>          <!-- NO afectado -->

Es el combinador más usado, pero también el más impreciso: selecciona todos los descendientes sin importar cuántos niveles haya entre ellos. En estructuras complejas puede aplicarse a más elementos de los previstos.

Hijo directo (>)

El signo > limita la selección al hijo inmediato, sin descender más:

/* Solo los <li> directamente dentro de .menu, no los anidados */
.menu > li {
    display: inline-block;
}

/* Solo el primer nivel de párrafos en .contenido */
.contenido > p {
    margin-bottom: 1.5rem;
}
<ul class="menu">
    <li>Inicio</li>                   <!-- afectado -->
    <li>Productos
        <ul>
            <li>Categoría A</li>      <!-- NO afectado -->
        </ul>
    </li>                             <!-- afectado (el <li> exterior) -->
</ul>

Útil en menús multinivel donde el primer nivel tiene un estilo y los submenús otro distinto. Con el combinador de descendiente, el estilo se filtraría a todos los niveles.

Adyacente (+)

El signo + selecciona el elemento que aparece inmediatamente después de otro, siendo ambos hijos del mismo padre:

/* El párrafo que sigue inmediatamente a un h2 */
h2 + p {
    font-size: 1.1rem;
    color: #555;
}

/* La etiqueta <label> que sigue a un <input> de tipo checkbox */
input[type="checkbox"] + label {
    cursor: pointer;
    padding-left: 0.5rem;
}
<h2>Título</h2>
<p>Este párrafo sigue al h2.</p>    <!-- afectado -->
<p>Este no sigue al h2.</p>         <!-- NO afectado -->

Solo afecta al elemento inmediatamente siguiente, no a todos los que vengan después. Si entre el h2 y el párrafo hubiera otro elemento (un div, por ejemplo), el párrafo ya no quedaría seleccionado.

Hermano general (~)

La virgulilla ~ selecciona todos los elementos que vienen después (no solo el inmediato), siempre que sean hijos del mismo padre:

/* Todos los <p> que vengan después de un <h2>, en el mismo nivel */
h2 ~ p {
    margin-left: 1rem;
}

/* Todos los elementos que siguen a un input:checked */
input:checked ~ .opciones-extra {
    display: block;
}
<h2>Título</h2>
<p>Primer párrafo.</p>    <!-- afectado -->
<div>Un div.</div>
<p>Segundo párrafo.</p>   <!-- afectado -->

La combinación input:checked ~ .elemento es la base de técnicas CSS puras de mostrar/ocultar contenido sin JavaScript: cuando el checkbox está marcado, el selector activa estilos en los elementos hermanos que le siguen.

Selectores de atributo

Los selectores de atributo apuntan a elementos según el valor de sus atributos HTML. Son más precisos que los selectores de elemento y no requieren añadir clases al marcado.

La sintaxis básica es el nombre del atributo entre corchetes:

/* Cualquier elemento que tenga el atributo href (sin importar el valor) */
[href] { color: blue; }

/* Cualquier input que tenga el atributo disabled */
[disabled] { opacity: 0.5; cursor: not-allowed; }

Para filtrar por el valor exacto, se añade ="valor":

/* Solo los inputs de tipo email */
input[type="email"] { padding-right: 2rem; }

/* Solo los links que abren en nueva pestaña */
a[target="_blank"] { ... }

Además del operador de igualdad exacta, hay seis operadores para hacer coincidir partes del valor:

Operador Significado Ejemplo
[attr="val"] Valor exactamente igual a "val" [type="submit"]
[attr^="val"] Valor empieza por "val" [href^="https"]
[attr$="val"] Valor termina en "val" [href$=".pdf"]
[attr*="val"] Valor contiene "val" en cualquier posición [href*="ejemplo"]
[attr~="val"] Valor es lista de palabras separadas por espacios y una de ellas es "val" [class~="activo"]
[attr|="val"] Valor es exactamente "val" o empieza por "val-" [lang|="es"]

Algunos ejemplos prácticos que ilustran cuándo son útiles:

/* Añadir icono a los PDFs sin tocar el HTML */
a[href$=".pdf"]::after {
    content: " ↓";
    font-size: 0.8em;
    color: #888;
}

/* Marcar todos los links externos */
a[href^="http"]::after {
    content: " ↗";
}

/* Inputs de texto, email, search, url (todos los que admiten texto libre) */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]) {
    border: 1px solid #ccc;
    padding: 0.5rem;
}

/* Selector de idioma: tanto lang="es" como lang="es-MX" */
[lang|="es"] {
    font-family: 'Segoe UI', sans-serif;
}

Los selectores de atributo tienen la misma especificidad que una clase (0-1-0), igual que los selectores de pseudoclase.

Combinando selectores

Los combinadores y los selectores de atributo se pueden encadenar con selectores de elemento, clase e ID para construir reglas muy precisas:

/* Un <a> con clase .activo dentro de .navegacion */
.navegacion a.activo {
    font-weight: bold;
    color: #84ba3f;
}

/* El primer <p> inmediatamente después de un h3 dentro de .articulo */
.articulo h3 + p {
    margin-top: 0.5rem;
}

/* Links a PDF dentro de la tabla de descargas */
.tabla-descargas a[href$=".pdf"] {
    color: #c0392b;
}

/* Todos los campos de formulario deshabilitados dentro de .formulario-bloqueado */
.formulario-bloqueado input[disabled],
.formulario-bloqueado select[disabled],
.formulario-bloqueado textarea[disabled] {
    background: #f0f0f0;
}

La clave es no abusar de la especificidad. Un selector como .nav > ul > li > a.activo funciona, pero es frágil: cualquier cambio en la estructura HTML lo rompe. Cuando la cadena de combinadores se alarga, suele ser señal de que conviene añadir una clase al elemento final y usar un selector más corto.

Recapitulación

  • El combinador de descendiente (espacio) selecciona elementos a cualquier profundidad dentro del padre. El de hijo directo (>) limita la selección al primer nivel.
  • El adyacente (+) selecciona solo el elemento inmediatamente siguiente al mismo nivel. El hermano general (~) selecciona todos los que vienen después al mismo nivel.
  • Los selectores de atributo ([attr]) apuntan a elementos por la presencia o valor de sus atributos, sin requerir clases en el HTML.
  • Los operadores de atributo permiten coincidencias parciales: ^= (empieza por), $= (termina en), *= (contiene), ~= (palabra en lista), |= (valor o valor con guion).
  • Los selectores de atributo tienen especificidad (0-1-0), igual que las clases.
  • Los combinadores y atributos se encadenan con otros selectores. Cadenas largas de combinadores indican que conviene simplificar añadiendo una clase al elemento destino.

Tu proyecto

Refina los selectores CSS de estilos.css aprovechando combinadores y atributos para no añadir clases innecesarias al HTML:

/* Solo los enlaces dentro de nav, sin clase extra */
.nav-principal > a {
    text-decoration: none;
    color: inherit;
}

/* El primer hijo de cada sección recibe menos margen superior */
main section > *:first-child {
    margin-top: 0;
}

/* El enlace mailto del footer con icono implícito */
a[href^="mailto:"] {
    font-style: italic;
}

/* Imagen del proyecto: solo las que están dentro de .tarjeta */
.tarjeta > img {
    display: block;
    width: 100%;
}

Prueba en el inspector: selecciona uno de estos elementos y confirma en "Computed styles" que las reglas nuevas se están aplicando.

En la próxima lección: pseudoclases y pseudoelementos: estilos según el estado del elemento (hover, focus, validación) y partes virtuales del DOM.

TOP