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
- Container Queries
- Cascade Layers
- Selector :has()
- Subgrid
- Nuevas funciones de color
- Scroll-driven animations
- 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.

