Pieza única de ajedrez iluminada

Optimizando la singularidad en CSS con :only-of-type

Briefing: hoy dejamos de pedir permisos al JS para crear UIs coherentes. :only-of-type te da la señal que necesitas sin clases basura ni lógica duplicada. Si dudas, repasa arquitectura CSS y especificidad sin drama. Luego cargamos y disparamos.

Acto I — El crimen: la clase que delata tu miedo

La escena: un listado de cards. Con una sola, debería ocupar todo el ancho y ocultar adornos. ¿Qué hace la medianía? Mete JS para colgar un is-single como muleta, y que sea lo que Dios quiera.
Consecuencia: lógica en dos sitios, estilos frágiles, bugs al mínimo cambio de marcado. Velocidad quemada, foco de UX perdido, deuda técnica acumulándose. Consulta el parte de bajas en antipatrones CSS que rompen la UI.

Antes (cinta adhesiva y rezos):

CSS
   
/* Eres rehén de una clase “estado” que alguien debe acordarse de poner */
.list.is-single .card { 
  width: 100%; 
}
.list .card { 
  width: calc(50% - 1rem); 
}
.list .card:nth-child(odd) { 
  margin-right: 1rem; 
}
.list.is-single .separator { 
  display: none; 
}
   
JS 
const list = document.querySelector('.list');
const cards = list.querySelectorAll('article.card');
if (cards.length === 1) { 
  list.classList.add('is-single'); 
}
HTML
   
<section class="list">
  <article class="card">
    ...
  </article>

  <hr class="separator" />
</section>
   

Diagnóstico: has externalizado al runtime una decisión que el DOM ya sabe. Peor aún: amarras diseño a una clase que cualquiera puede olvidar. El sistema queda a merced del despiste. Y el despiste siempre llega.

Acto II — El arma: lo que hace un “no medianía”

Herramienta: :only-of-type.
Filosofía: el DOM habla, CSS escucha, JS no interfiere. Si un elemento es el único de su especie, el estilo actúa. Sin clases de estado. Sin ifs. Con especificidad contenida (lee specificity) y soporte moderno (valida en Can I use). Para letra pequeña de la norma, Selectors Level 4.

Acto III — El manual de campo: aplícalo en 15 minutos

Objetivo: mismo marcado semántico para ítems comparables (todos <article>), layout con grid, y reglas condicionales con :only-of-type. Cero JS.

Después (limpio, resiliente, medible):

HTML
   
<section class="list">
  <article class="card">
    ...
  </article>

  <!-- Añade más <article.card> si existen; el resto de hijos pueden ser <hr>, <aside>, etc. -->
  <hr class="separator" />
</section>
   
CSS
   
/* Layout base: grid de dos columnas en desktop */
.list { 
  display: grid; 
  grid-template-columns: repeat(2, minmax(0, 1fr)); 
  gap: 1rem; 
}

/* El único <article.card> se expande a todo el ancho, sin tocar HTML */
.list > article.card:only-of-type { 
  grid-column: 1 / -1; 
  margin-inline: auto; 
}

/* Si hay una sola card, oculta decoraciones vecinas sin JS */
.list > article.card:only-of-type ~ .separator { 
  display: none; 
}

/* Responsive sin dramas */
@media (max-width: 640px) { 
  .list { 
    grid-template-columns: 1fr; 
  } 
}
   

Checklist de despliegue (sí, en 15 minutos):

  • Da uniformidad al tag de tus ítems: si son tarjetas, que sean <article>. Si mezclas <article> y <section>, :only-of-type no saltará si hay dos <article>. Esto va de diseño semántico. Refuérzalo en diseño semántico HTML.
  • No confundas :only-of-type con :only-child: el primero permite hermanos de otros tipos; el segundo no permite ninguno. Revisa más combinaciones en pseudoclases estructurales.
  • Especificidad bajo control: pseudoclases suman poco; evita !important.
  • Soporte: parques modernos OK; si hay legado, define degradado aceptable (p. ej., ancho fijo) y documenta en tu checklist de degradados graciosos.
  • Mide el impacto: menos JS en el critical path = TTI y CLS más estables. Conecta los cambios a tu dash de observabilidad front.

Patrones rápidos que huelen a dinero

  • Galería con una sola imagen (evita marcos vacíos):
    CSS
       
    .gallery { 
      display: grid; 
      place-items: center; 
    }
    .gallery > img:only-of-type { 
      max-width: min(100%, 720px); 
      height: auto; 
    }
       
  • Footer con un solo CTA (hazlo imposible de ignorar):
    CSS
       
    footer .cta:only-of-type { 
      display: block; 
      width: 100%; 
      padding: .9rem 1.2rem; 
      font-size: 1.125rem; 
    }
       
  • Listas con un solo <li> (quita ruido automático):
    CSS
       
    ul > li:only-of-type { 
      list-style: none; 
      text-align: center; 
      margin: 0; 
    }
       
  • Cards con adyacentes ruidosos (silencia lo accesorio):
    CSS
       
    /* Si la única card va seguida de banners o líneas, apágalos sin tocar el DOM */
    .list > article.card:only-of-type ~ .banner,
    .list > article.card:only-of-type ~ .separator { 
      display: none; 
    }
       

Validación relámpago: testea como si te fuera la nómina

  • Monta el componente con 0, 1 y N ítems en tu plantilla de pruebas: plantilla de pruebas visuales. El CSS debe autoajustar sin tocar el DOM.
  • Revisa que no introduces layout shift al pasar de 1 a N elementos. Si ves brincos, visita CLS a cero.
  • Documenta la decisión en el README del componente: motivo, soporte y degradado. Deja trazabilidad en tu estándar de commits.

Acto IV — La sentencia: ley grabada en la hoja de estilos

Nueva regla de la casa: si necesitas saber si “hay uno solo”, primero lo decide CSS. Prohibido colgar clases “is-single” salvo que demuestres por escrito por qué :only-of-type no basta.
Principio maestro: diseña con el DOM, estiliza con reglas que se adaptan, y deja al JS para lo que factura. Lo demás es ruido. Y el ruido ya no pasa por esta puerta.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *