Ir al contenido principal

Técnicas CSS modernas en 2026

Explore las últimas características y técnicas CSS que están transformando la forma en que construimos interfaces de usuario en la web.

Sarah Chen Sarah Chen
10 min de lectura
Ilustración: un componente de tarjeta que se reorganiza en tres tamaños de contenedor

Varios problemas que antes requerían soluciones alternativas en JavaScript tienen hoy soluciones nativas en CSS.

El hilo común de esta lista es que cada función sustituye a un apaño: un resize observer, una clase que se activa desde un script, un preprocesador, un truco de especificidad. La mayoría ya tienen soporte en todos los motores de navegador principales. Donde el soporte todavía es desigual, se indica, junto con una alternativa razonable.

Container queries

Las container queries han cambiado radicalmente el diseño responsive. En lugar de depender únicamente de las dimensiones del viewport, los componentes ahora pueden adaptarse según el tamaño de su contenedor padre. Esto hace posibles componentes realmente reutilizables: una tarjeta que responde al espacio que ocupa y no al ancho de la pantalla.

.card-container {
  container-type: inline-size;
}

@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

Dos detalles hacen que las container queries sean más útiles en la práctica.

Ponga nombre a sus contenedores. Los contenedores anidados son habituales en los layouts reales, y una consulta @container sin nombre coincide con el contenedor ancestro más cercano. Ponerles nombre elimina la ambigüedad:

.sidebar {
  container: sidebar / inline-size;
}

@container sidebar (min-width: 320px) {
  .nav-item { flex-direction: row; }
}

Use las unidades de container query. cqi equivale al 1 % del tamaño en línea del contenedor, así que font-size: clamp(1rem, 4cqi, 1.5rem) escala un encabezado con su tarjeta y no con la ventana.

Las container style queries son el siguiente paso: @container style(--variant: compact) permite que un componente reaccione a una custom property definida por un ancestro, una alternativa más limpia a las clases modificadoras. El soporte de las style queries todavía no es universal, así que mantenga un valor por defecto sin consulta.

Una advertencia: container-type: inline-size aplica contención de tamaño en el eje en línea, por lo que el elemento ya no puede dimensionarse a partir del ancho de su contenido. Aplíquelo a un contenedor cuyo ancho venga del layout, como una celda de grid, y no a un elemento que se ajusta a su contenido.

El selector :has()

Conocido a menudo como el “selector de padre”, :has() permite estilos que dependen del estado de los descendientes o de los hermanos. ¿Necesita dar un estilo distinto a un grupo de formulario cuando contiene un campo no válido? :has() lo hace trivial sin intervención de JavaScript.

.field:has(input:user-invalid) {
  --field-border: var(--color-danger);
}

.card:has(img) {
  grid-template-rows: auto 1fr;
}

/* Impide el desplazamiento de la página mientras hay un diálogo modal abierto */
html:has(dialog[open]:modal) {
  overflow: hidden;
}

Vale la pena adoptar junto a él la pseudoclase :user-invalid de la primera regla: a diferencia de :invalid, solo coincide después de que el usuario haya interactuado con el campo, de modo que los campos obligatorios vacíos no se muestran como errores al cargar la página.

:has() también puede mirar hacia los lados. h2:has(+ p) coincide con un encabezado seguido inmediatamente de un párrafo, algo útil para ajustar el espaciado en textos largos. Mantenga los selectores anclados a un elemento concreto en lugar de escribir comprobaciones body:has(...) contra todo el documento, tanto por legibilidad como para que el navegador tenga menos trabajo de invalidación.

Cascade layers

Las declaraciones @layer dan a los desarrolladores un control explícito sobre la cascada y resuelven las guerras de especificidad que han plagado las hojas de estilo durante décadas. Organice sus estilos en capas de reset, base, componentes y utilidades para obtener resultados predecibles independientemente del orden en el código fuente.

@layer reset, base, components, utilities;

@layer components {
  .button { background: var(--color-primary); }
}

@layer utilities {
  .bg-transparent { background: transparent; }
}

Manda el orden declarado en la primera línea: una regla en utilities gana a una regla en components aunque el selector del componente sea más específico. La especificidad solo importa entre reglas de la misma capa.

Dos comportamientos sorprenden a menudo:

  • Los estilos sin capa ganan a todos los estilos con capa en las declaraciones normales. Si importa una hoja de estilo de terceros sin capa, sobrescribirá los estilos de sus componentes en capas. Envuélvala en su lugar: @import url("vendor.css") layer(vendor);.
  • !important invierte el orden de las capas. Una declaración importante en una capa temprana gana a una de una capa posterior, lo que permite que un reset proteja deliberadamente una regla crítica.

Tailwind CSS v4 está construido sobre cascade layers nativas, una buena señal de que las capas están listas para una arquitectura en producción.

Subgrid

Las rejillas de tarjetas arrastran desde hace tiempo un defecto conocido: los títulos de distinta longitud desplazan el cuerpo y el botón de cada tarjeta a alturas diferentes. subgrid permite que un grid hijo adopte las pistas de su padre, de modo que los elementos de tarjetas hermanas quedan alineados.

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 1.5rem;
}

.card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
}

Cada tarjeta ocupa tres filas del padre y transmite esas filas hacia abajo, así que todos los títulos, cuerpos y pies de una fila comparten altura. Subgrid también hereda por defecto el gap del padre, que puede sobrescribir en el hijo.

Color: oklch() y color-mix()

oklch() describe el color mediante luminosidad perceptual, croma y tono. Su ventaja práctica frente a hsl() es que valores de luminosidad iguales se perciben realmente igual de claros, de modo que una paleta construida variando la luminosidad se comporta de forma predecible entre tonos. También puede expresar colores en gamas más amplias, como Display P3.

:root {
  --brand: oklch(62% 0.19 255);
  --brand-hover: color-mix(in oklch, var(--brand), black 15%);
  --brand-subtle: color-mix(in oklch, var(--brand) 12%, transparent);
}

color-mix() deriva tintes, sombras y variantes translúcidas a partir de un único token, de modo que un tema puede cambiar una sola variable en lugar de una docena. La sintaxis de color relativo va más allá y le permite ajustar un canal de un color existente: oklch(from var(--brand) calc(l + 0.1) c h) aclara un color sin tocar su tono.

Combínelos con light-dark() y color-scheme: light dark para definir los valores de ambos temas en una sola declaración, como color: light-dark(#1a1a1a, #f2f2f2).

View transitions

La View Transitions API aporta animaciones nativas de transición entre páginas a las aplicaciones de varias páginas. Combinada con el componente <ClientRouter /> de Astro, crear experiencias de navegación fluidas, similares a las de una app, requiere muy poco código y ofrece un acabado pulido.

Existen dos variantes. Las transiciones dentro del mismo documento, iniciadas desde un script con document.startViewTransition(), son las que usan los routers del lado del cliente. Las transiciones entre documentos no necesitan JavaScript en absoluto; ambas páginas las activan desde CSS:

@view-transition {
  navigation: auto;
}

.post-hero {
  view-transition-name: post-hero;
}

Asignar el mismo view-transition-name a elementos equivalentes de la página antigua y la nueva hace que el navegador transforme uno en el otro. Las transiciones entre documentos todavía no tienen soporte en todos los motores, pero los navegadores sin soporte simplemente navegan con normalidad, lo que las convierte en una mejora progresiva segura. Envuelva las animaciones de transición personalizadas en @media (prefers-reduced-motion: no-preference) para que los usuarios que piden menos movimiento lo obtengan.

Anidamiento

El anidamiento nativo de CSS elimina la necesidad de preprocesadores en muchos proyectos. Escriba estilos que reflejen la estructura de su HTML sin recurrir a Sass o Less, y mantenga su cadena de herramientas más ligera.

.card {
  padding: 1.5rem;

  & h2 {
    margin-block-end: 0.5rem;
  }

  &:hover {
    box-shadow: var(--shadow-md);
  }

  @media (min-width: 48rem) {
    padding: 2rem;
  }
}

Hay diferencias con Sass. No se pueden concatenar selectores, así que &__title al estilo BEM no funciona. Además, una regla anidada se trata como :is(), que adopta la especificidad de su argumento más específico, de modo que anidar bajo una lista de selectores puede aumentar la especificidad de formas en que Sass no lo haría. Mantenga el anidamiento poco profundo; dos niveles suelen ser más que suficientes.

Ámbitos con @scope

@scope limita un conjunto de reglas a un subárbol y, opcionalmente, se detiene en un límite interior:

@scope (.card) to (.card-content) {
  img { border-radius: 0.5rem; }
}

Aquí las imágenes dentro de una tarjeta se redondean, pero las imágenes dentro del slot de contenido de la tarjeta no se tocan. También añade la proximidad del ámbito a la cascada: cuando dos reglas con ámbito entran en conflicto con la misma especificidad, gana aquella cuya raíz de ámbito está más cerca del elemento. El soporte llegó a Firefox más tarde que a Chromium y Safari, así que revise su matriz de navegadores antes de depender de él para algo crítico.

Anchor positioning

Los tooltips y los menús desplegables han necesitado tradicionalmente una librería de posicionamiento en JavaScript. El anchor positioning de CSS permite que un elemento se posicione de forma declarativa respecto a otro:

.trigger {
  anchor-name: --menu-trigger;
}

.menu {
  position: absolute;
  position-anchor: --menu-trigger;
  position-area: block-end span-inline-end;
  position-try-fallbacks: flip-block;
}

position-try-fallbacks indica al navegador que coloque el menú por encima del disparador cuando no hay espacio debajo, que es precisamente el caso límite para el que existen las librerías de posicionamiento. Se combina de forma natural con el atributo popover para cerrar el menú al hacer clic fuera. El anchor positioning llegó primero a Chromium y los demás motores lo han incorporado más recientemente, así que trátelo como una mejora: mantenga una posición alternativa sencilla y verifique el soporte actual antes de prescindir de una librería.

Pequeñas mejoras que vale la pena adoptar

Unas cuantas funciones menores merecen un lugar en cualquier hoja de estilo nueva:

  • text-wrap: balance iguala la longitud de las líneas en los encabezados y evita que una sola palabra quede huérfana en la última línea.
  • @property registra una custom property con un tipo, de modo que se puede animar, como el ángulo de un degradado.
  • margin-inline, padding-block, inset-inline-start y las demás propiedades lógicas hacen que los layouts se adapten a los idiomas de derecha a izquierda sin reglas adicionales.
  • :focus-visible muestra los anillos de foco a los usuarios de teclado sin mostrarlos en cada clic del ratón.

Adoptar nuevas funciones con seguridad

La mejor forma de usar todo esto es de manera incremental. Compruebe el estado Baseline de una función antes de tratarla como un requisito y recurra a @supports cuando la alternativa necesite reglas distintas:

@supports not (anchor-name: --a) {
  .menu { inset-block-start: 100%; }
}

Las capas, las container queries, :has(), el anidamiento y las funciones de color modernas son hoy bases fiables. Las view transitions, @scope y el anchor positioning son mejoras excelentes. Juntas permiten que CSS se encargue de una gran parte del trabajo que antes requería JavaScript, con menos código que enviar y menos que mantener.

Compartir este artículo
frontend design-systems accessibility