wandres.dev
GRID O FLEX · El árbol de decisión

Combinarlos: cuándo la respuesta correcta son los dos

Grid para la estructura, Flex para los grupos: los patrones de anidamiento que resuelven la tarjeta con pie alineado, la barra dentro de la plantilla y el ítem de grid que es contenedor flex.

⏱ 18 min

La pregunta “Grid o Flex” tiene truco porque presupone que la respuesta es una sola para todo el árbol del documento, y no lo es: cada contenedor toma su propia decisión. Un elemento puede ser ítem de grid por fuera y contenedor flex por dentro sin ninguna tensión, porque el módulo de layout describe cómo un elemento coloca a sus hijos, no cómo se comporta él. En la práctica, casi todo componente no trivial de 2026 es una rejilla exterior con grupos flex dentro.

🎯 Al terminar esta lección sabrás
  • Anidar contenedores de layout entendiendo que cada nivel decide de forma independiente.
  • Construir la tarjeta con pie alineado usando grid-template-rows con 1fr en el cuerpo.
  • Combinar una plantilla de página en Grid con barras y grupos en Flex.
  • Reconocer los dos anidamientos que sí dan problemas y por qué.

Cada contenedor decide por su cuenta

Un elemento tiene dos papeles simultáneos e independientes: el de participante en el layout de su padre y el de contenedor del layout de sus hijos. display: grid sobre un ítem de flex no entra en conflicto con nada; simplemente ese ítem, que su padre coloca según las reglas de flex, coloca a sus propios hijos según las reglas de grid.

.pagina {                       /* nivel 1: rejilla de pagina */
  display: grid;
  grid-template-columns: minmax(0, 1fr) min(70ch, 100%) minmax(0, 1fr);
}
.pagina > * { grid-column: 2; }  /* todo al carril central */
.pagina > .ancho-total { grid-column: 1 / -1; }

.barra {                         /* nivel 2: fila flexible */
  display: flex;
  align-items: center;
  gap: 1rem;
}
.barra .separador { margin-inline-start: auto; }  /* empuja lo que sigue */

Ese min(70ch, 100%) en la columna central es un carril de lectura con escape lateral: cualquier hijo con grid-column: 1 / -1 se sale a ancho completo sin necesidad de contenedores anidados ni márgenes negativos. Y margin-inline-start: auto dentro de la barra es la forma canónica de partir un grupo flex en dos bloques sin añadir un div intermedio: los márgenes automáticos en Flexbox absorben todo el espacio libre antes de que justify-content llegue a repartirlo.

La tarjeta con pie alineado

Es el patrón que más veces vas a escribir, y el que mejor demuestra la combinación. La colección de tarjetas es una rejilla. Cada tarjeta, por dentro, es otra rejilla de tres filas donde el cuerpo absorbe el espacio sobrante para que el pie quede pegado abajo aunque los textos tengan longitudes distintas.

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

.tarjeta {
  display: grid;
  grid-template-rows: auto 1fr auto;   /* cabecera, cuerpo elastico, pie */
  gap: 0.75rem;
  padding: 1.25rem;
  border: 1px solid color-mix(in oklch, currentColor 20%, transparent);
  border-radius: 0.75rem;
}

.tarjeta .acciones {                    /* el pie es un grupo flexible */
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}
.tarjeta .acciones .secundaria { margin-inline-start: auto; }

Tres niveles, tres decisiones distintas y ningún conflicto: la colección necesita pistas compartidas (Grid), la tarjeta necesita que una fila absorba el sobrante (Grid, porque 1fr en una fila es exactamente eso), y las acciones necesitan que cada botón mida lo que mida su texto (Flex).

La alternativa con Flexbox para la tarjeta —flex-direction: column y margin-block-start: auto en el pie— funciona igual de bien y es igual de legítima. La versión con grid-template-rows gana cuando además quieres que las tres filas midan lo mismo en todas las tarjetas de la colección, y eso solo se consigue con subgrid.

💡
Subgrid es la costura entre los dos niveles

Si las tarjetas deben alinear sus cabeceras y sus pies entre sí, no basta con que cada una sea una rejilla: hay que hacer que hereden las filas de la colección con grid-template-rows: subgrid y grid-row: span 3. Es el único mecanismo que propaga coordenadas de un nivel de anidamiento al siguiente; ni Flexbox ni una rejilla independiente pueden hacerlo.

La plantilla completa

El esqueleto de aplicación típico combina los dos módulos en cuatro niveles y sigue leyéndose bien:

.app {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
  grid-template-areas:
    "lateral cabecera"
    "lateral contenido";
  block-size: 100dvh;
}
.app > .lateral   { grid-area: lateral; inline-size: 16rem; overflow-y: auto; }
.app > .cabecera  { grid-area: cabecera; }
.app > .contenido { grid-area: contenido; overflow-y: auto; }

/* dentro de la cabecera: una fila con separacion */
.cabecera {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-inline: 1rem;
  block-size: 3.5rem;
}

/* dentro del lateral: una columna con secciones */
.lateral nav {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.75rem;
}

Los minmax(0, 1fr) de las pistas y los overflow-y: auto en las zonas desplazables son lo que hace que esto no reviente: sin el mínimo cero, el contenido largo empujaría la pista y la barra de desplazamiento aparecería en el documento entero en vez de en el panel.

Los dos anidamientos que sí duelen

Ítem de flex con contenido ancho. Un contenedor flex hijo de otro contenedor flex, con texto o una tabla dentro, hereda el problema del tamaño mínimo automático en cada nivel. Cada elemento intermedio necesita su min-width: 0 (o min-inline-size: 0), porque el mínimo automático se aplica en todos los niveles y basta con que falte en uno para que el desbordamiento suba hasta arriba. Es el bug de layout más denunciado y más malinterpretado de Flexbox.

.fila { display: flex; gap: 1rem; }
.fila > .columna { flex: 1; min-inline-size: 0; }   /* imprescindible */
.fila > .columna .truncado {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

display: contents como pegamento. Es tentador usarlo para que los hijos de un envoltorio participen en la rejilla del abuelo. Funciona a nivel de layout, pero el envoltorio desaparece como caja: pierdes su fondo, su borde, su padding, su posibilidad de ser contenedor de consultas y su capacidad de crear un contexto de apilamiento. Además, históricamente rompía la semántica de accesibilidad en algunos elementos. Úsalo con conocimiento de causa, no como atajo por defecto.

El anidamiento no es una concesión: es el diseño correcto

Hay una intuición equivocada muy extendida de que anidar contenedores de layout es una señal de que algo se ha hecho mal, herencia de la época en que cada div extra costaba un reflow caro y el marcado se medía en kilobytes. La realidad del motor moderno es la contraria: cada contenedor de layout es un problema de optimización local que el navegador resuelve de forma independiente, y cuanto más pequeños y acotados sean esos problemas, más barato y más estable es el resultado. Una rejilla de página con tres barras flex dentro genera cuatro cálculos pequeños; una única rejilla monstruosa que intente coordinar todo genera uno grande cuyo resultado depende de todo a la vez, y donde tocar una celda puede mover cualquier otra. La lección profunda es la misma que en cualquier arquitectura de software: prefieres composición de restricciones locales a una restricción global, no por elegancia, sino porque el coste del cambio crece con el alcance de lo que un cambio puede afectar. Cuando dudes entre una rejilla complicada y dos sencillas anidadas, elige siempre las dos sencillas.