Volver al blog

CSS Moderno en 2025: Container Queries, Cascade Layers y Nuevas Features

Hola HaWkers, CSS evolucionó dramáticamente en los últimos años. Features que antes necesitaban JavaScript o workarounds complejos ahora son nativas y bien soportadas.

En 2025, escribir CSS es más poderoso y placentero que nunca. Vamos a explorar las features más impactantes que debes dominar.

El Estado del CSS en 2025

Soporte de Navegadores

La buena noticia: las features que vamos a ver tienen soporte excelente en todos los navegadores modernos:

  • Chrome/Edge: 100%
  • Firefox: 100%
  • Safari: 100%

Features que Cubriremos

  1. Container Queries
  2. Cascade Layers
  3. Selector :has()
  4. Subgrid
  5. Nuevas funciones de color
  6. Scroll-driven animations
  7. View Transitions

1. Container Queries

Container Queries fueron la feature más esperada de CSS. Permiten estilizar elementos basados en el tamaño de su container, no del viewport.

Sintaxis Básica

/* Definir container */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* Query basado en tamaño del container */
@container card (min-width: 400px) {
  .card {
    display: flex;
    flex-direction: row;
  }

  .card-image {
    width: 40%;
  }

  .card-content {
    width: 60%;
  }
}

@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }

  .card-image {
    width: 100%;
    aspect-ratio: 16/9;
  }
}

Ejemplo Práctico: Card Responsiva

<div class="card-container">
  <article class="card">
    <img class="card-image" src="image.jpg" alt="Descripción">
    <div class="card-content">
      <h3 class="card-title">Título del Card</h3>
      <p class="card-description">Descripción del contenido...</p>
      <button class="card-button">Saber más</button>
    </div>
  </article>
</div>
.card-container {
  container-type: inline-size;
}

.card {
  display: grid;
  gap: 1rem;
  background: white;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
}

.card-image {
  width: 100%;
  object-fit: cover;
}

.card-content {
  padding: 1rem;
}

/* Layout pequeño (menos de 300px) */
@container (max-width: 299px) {
  .card {
    grid-template-rows: auto 1fr;
  }

  .card-title {
    font-size: 1rem;
  }

  .card-description {
    display: none; /* Ocultar en tamaños muy pequeños */
  }
}

/* Layout mediano (300px - 500px) */
@container (min-width: 300px) and (max-width: 500px) {
  .card {
    grid-template-rows: 200px 1fr;
  }

  .card-title {
    font-size: 1.25rem;
  }
}

/* Layout grande (más de 500px) */
@container (min-width: 500px) {
  .card {
    grid-template-columns: 250px 1fr;
    grid-template-rows: auto;
  }

  .card-image {
    height: 100%;
    min-height: 200px;
  }

  .card-title {
    font-size: 1.5rem;
  }
}

2. Cascade Layers

Cascade Layers dan control total sobre la especificidad del CSS, resolviendo guerras de especificidad de una vez.

Sintaxis Básica

/* Definir orden de layers */
@layer reset, base, components, utilities;

/* Estilos en layers */
@layer reset {
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
}

@layer base {
  body {
    font-family: system-ui, sans-serif;
    line-height: 1.5;
  }

  h1, h2, h3 {
    line-height: 1.2;
  }
}

@layer components {
  .button {
    padding: 0.5rem 1rem;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  .button-primary {
    background: blue;
    color: white;
  }
}

@layer utilities {
  .text-center { text-align: center; }
  .mt-4 { margin-top: 1rem; }
  .hidden { display: none; }
}

Organizando Proyecto Real

/* main.css */

/* 1. Definir layers en orden de prioridad (menor a mayor) */
@layer reset, base, layout, components, features, utilities, overrides;

/* 2. Importar archivos en layers */
@import url('reset.css') layer(reset);
@import url('base.css') layer(base);
@import url('layout.css') layer(layout);

/* 3. Componentes pueden definir sus propios sub-layers */
@layer components {
  @layer buttons, cards, forms;
}

@layer components.buttons {
  .btn {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 1rem;
    border-radius: 4px;
    font-weight: 500;
    transition: all 0.2s;
  }

  .btn-primary {
    background: var(--color-primary);
    color: white;
  }

  .btn-primary:hover {
    background: var(--color-primary-dark);
  }
}

@layer components.cards {
  .card {
    background: white;
    border-radius: 8px;
    box-shadow: var(--shadow-sm);
  }
}

/* Utilities siempre ganan (excepto overrides) */
@layer utilities {
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
  }
}

/* Overrides para casos especiales */
@layer overrides {
  .force-dark {
    background: black !important;
    color: white !important;
  }
}

3. Selector :has()

El "selector padre" que CSS nunca tuvo. Permite estilizar elementos basados en sus descendientes.

Ejemplos Prácticos

/* Formulario con campos inválidos */
.form-group:has(input:invalid) {
  border-color: red;
}

.form-group:has(input:invalid) .form-label {
  color: red;
}

/* Card con imagen vs sin imagen */
.card:has(img) {
  grid-template-rows: 200px 1fr;
}

.card:not(:has(img)) {
  grid-template-rows: 1fr;
  padding-top: 1rem;
}

/* Navegación con dropdown abierto */
.nav-item:has(.dropdown:hover) {
  background: var(--color-primary-light);
}

/* Checkbox hackeado para toggle */
.toggle-container:has(input:checked) .toggle-content {
  display: block;
}

/* Estilizar tabla con filas seleccionadas */
table:has(tr.selected) {
  border: 2px solid var(--color-primary);
}

table:has(tr.selected) thead {
  background: var(--color-primary);
  color: white;
}

Caso de Uso: Dark Mode sin JavaScript

<html>
<head>
  <style>
    :root:has(#dark-mode:checked) {
      --bg: #1a1a2e;
      --text: #eee;
      --accent: #00d4ff;
    }

    :root:not(:has(#dark-mode:checked)) {
      --bg: #fff;
      --text: #333;
      --accent: #0066ff;
    }

    body {
      background: var(--bg);
      color: var(--text);
      transition: background 0.3s, color 0.3s;
    }

    #dark-mode {
      position: absolute;
      opacity: 0;
    }

    .theme-toggle {
      cursor: pointer;
      padding: 0.5rem 1rem;
      background: var(--accent);
      color: var(--bg);
      border-radius: 4px;
    }
  </style>
</head>
<body>
  <input type="checkbox" id="dark-mode">
  <label class="theme-toggle" for="dark-mode">Toggle Theme</label>
  <!-- contenido -->
</body>
</html>

4. Subgrid

Subgrid permite que elementos hijos hereden las tracks del grid padre.

/* Grid padre */
.page-layout {
  display: grid;
  grid-template-columns:
    [full-start] minmax(1rem, 1fr)
    [content-start] min(65ch, 100% - 2rem)
    [content-end] minmax(1rem, 1fr)
    [full-end];
  gap: 1rem;
}

/* Elementos que usan las columnas del padre */
.page-layout > * {
  grid-column: content;
}

/* Elementos full-width */
.page-layout > .full-width {
  grid-column: full;
}

/* Card con subgrid para alinear contenido */
.cards-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2rem;
}

.card {
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  gap: 0.5rem;
}

/* Con subgrid, cards se alinean perfectamente */
.cards-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2rem;
}

.card-with-subgrid {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 4; /* Span 4 filas del padre */
}

Ejemplo: Lista de Productos Alineados

.products-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  /* Grid implícito de 4 filas por producto */
  grid-auto-rows: auto auto 1fr auto;
  gap: 1.5rem;
}

.product-card {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 4;
  background: white;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 4px rgb(0 0 0 / 0.1);
}

.product-image {
  aspect-ratio: 1;
  object-fit: cover;
}

.product-title {
  padding: 0 1rem;
  font-weight: 600;
}

.product-description {
  padding: 0 1rem;
  color: #666;
  font-size: 0.875rem;
}

.product-price {
  padding: 1rem;
  font-size: 1.25rem;
  font-weight: 700;
  color: var(--color-primary);
}

5. Nuevas Funciones de Color

CSS ahora tiene funciones poderosas para manipular colores.

:root {
  /* Colores base */
  --primary: oklch(65% 0.2 250);
  --secondary: oklch(70% 0.15 150);

  /* Variaciones automáticas */
  --primary-light: oklch(from var(--primary) calc(l + 0.1) c h);
  --primary-dark: oklch(from var(--primary) calc(l - 0.15) c h);

  /* Color complementario */
  --primary-complement: oklch(from var(--primary) l c calc(h + 180));
}

/* color-mix() para mesclar colores */
.overlay {
  background: color-mix(in oklch, var(--primary) 80%, transparent);
}

.button-hover {
  background: color-mix(in oklch, var(--primary), black 20%);
}

/* Paleta automática */
.color-scale {
  --base: oklch(50% 0.2 250);

  --100: oklch(from var(--base) 95% c h);
  --200: oklch(from var(--base) 85% c h);
  --300: oklch(from var(--base) 75% c h);
  --400: oklch(from var(--base) 65% c h);
  --500: var(--base);
  --600: oklch(from var(--base) 45% c h);
  --700: oklch(from var(--base) 35% c h);
  --800: oklch(from var(--base) 25% c h);
  --900: oklch(from var(--base) 15% c h);
}

6. Scroll-Driven Animations

Animaciones basadas en scroll, sin JavaScript.

/* Barra de progreso de lectura */
.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: var(--color-primary);
  transform-origin: left;
  animation: grow-progress linear;
  animation-timeline: scroll(root);
}

@keyframes grow-progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

/* Fade-in de elementos conforme scroll */
.fade-in-on-scroll {
  animation: fade-in linear;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}

@keyframes fade-in {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Parallax effect */
.parallax-bg {
  animation: parallax linear;
  animation-timeline: scroll();
}

@keyframes parallax {
  from { transform: translateY(0); }
  to { transform: translateY(-30%); }
}

7. View Transitions

Transiciones suaves entre estados de la página.

/* Estilo básico de transición */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.3s;
}

/* Transición customizada para elemento específico */
.hero-image {
  view-transition-name: hero;
}

::view-transition-old(hero),
::view-transition-new(hero) {
  animation-duration: 0.5s;
  animation-timing-function: ease-in-out;
}

/* Transición de página (slide) */
@keyframes slide-from-right {
  from { transform: translateX(100%); }
}

@keyframes slide-to-left {
  to { transform: translateX(-100%); }
}

::view-transition-old(root) {
  animation: slide-to-left 0.3s ease-in-out;
}

::view-transition-new(root) {
  animation: slide-from-right 0.3s ease-in-out;
}
// Usando View Transitions API
async function navigateTo(url) {
  if (!document.startViewTransition) {
    // Fallback para navegadores sin soporte
    window.location.href = url;
    return;
  }

  const transition = document.startViewTransition(async () => {
    const response = await fetch(url);
    const html = await response.text();
    document.body.innerHTML = html;
  });

  await transition.finished;
}

Conclusión

CSS en 2025 es increíblemente poderoso. Features que antes necesitaban JavaScript ahora son nativas, con mejor performance y accesibilidad.

Resumen de las features:

  • Container Queries: Componentes verdaderamente responsivos
  • Cascade Layers: Control total de especificidad
  • :has(): Selector padre finalmente disponible
  • Subgrid: Alineamiento perfecto en grids complejos
  • Colores: Manipulación avanzada nativa
  • Scroll Animations: Animaciones sin JavaScript
  • View Transitions: Transiciones de página suaves

Si quieres profundizar en desarrollo frontend moderno, recomiendo que veas otro artículo: Web APIs Modernas del Navegador donde vas a descubrir APIs JavaScript que complementan estas features CSS.

¡Vamos a por ello! 🦅

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios