La solución completa: tarjetas, formularios y la rejilla de página
Código pegable para los tres casos donde subgrid gana: tarjetas hermanas con contenido de longitud variable, formularios de dos columnas y contenido de artículo alineado con la rejilla global.
Con la sintaxis y las herencias ya cubiertas, esta lección es código. Tres casos completos, comprobables, que resuelven de verdad los problemas que motivaron subgrid: la fila de tarjetas cuyos elementos internos se alinean, el formulario de etiquetas y campos que se alinea sin tablas ni anchos fijos, y el artículo cuyo contenido comparte la rejilla de la página sin anidar contenedores.
- Escribir la solución completa del problema de las tarjetas.
- Construir un formulario alineado en columnas sin anchos fijos.
- Alinear el contenido de un artículo con la rejilla global de la página.
- Adaptar los tres casos cuando el número de elementos internos varía.
Tarjetas alineadas
El caso que abrió el nivel, resuelto. La clave es que la rejilla del contenedor define las filas que las tarjetas van a compartir, y cada tarjeta las hereda.
<ul class="planes">
<li class="plan">
<h3>Básico</h3>
<p class="desc">Para empezar.</p>
<p class="precio">9 euros</p>
<a class="cta" href="/basico">Elegir</a>
</li>
<li class="plan">
<h3>Profesional para equipos grandes</h3>
<p class="desc">Colaboración, control de accesos y soporte prioritario en horario laboral.</p>
<p class="precio">29 euros</p>
<a class="cta" href="/pro">Elegir</a>
</li>
<li class="plan">
<h3>Empresa</h3>
<p class="desc">Todo lo anterior más facturación anual.</p>
<p class="precio">A medida</p>
<a class="cta" href="/empresa">Elegir</a>
</li>
</ul>
.planes {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
grid-template-rows: auto auto auto auto; /* las cuatro filas compartidas */
gap: 1.5rem;
list-style: none;
margin: 0;
padding: 0;
}
.plan {
grid-row: span 4; /* abarca las cuatro */
display: grid;
grid-template-rows: subgrid; /* y las hereda */
gap: 0.75rem; /* gap propio dentro de la tarjeta */
padding: 1.25rem;
border: 1px solid #45475a;
border-radius: 0.75rem;
}
.plan h3 { margin: 0; font-size: 1.25rem; }
.plan .desc { margin: 0; color: #bac2de; }
.plan .precio{ margin: 0; font-size: 1.75rem; font-weight: 700; }
.plan .cta { align-self: end; justify-self: start; }
Los cuatro elementos internos se alinean entre las tres tarjetas: los títulos ocupan la misma altura, las descripciones empiezan a la misma altura, los precios están a la misma altura y los botones también. Y ningún texto se recorta: la altura de cada fila la fija el contenido más alto de las tres.
Fíjate en el gap: 0.75rem de la tarjeta. Sobrescribe el gap heredado de 1.5rem, con lo que la separación interna es menor que la separación entre tarjetas. Como el subgrid coloca sus líneas en el centro del hueco del padre, ese gap menor hace que el contenido de la tarjeta se acerque a los bordes de las pistas compartidas, que es exactamente lo que quieres visualmente.
Cuatro elementos internos, grid-template-rows: auto auto auto auto y grid-row: span 4. Si añades un quinto elemento a la tarjeta y no tocas los otros dos números, ese quinto cae en una pista implícita del subgrid y no se alinea con nada. Los tres números van juntos, siempre.
Formulario de dos columnas
Aquí el eje subdividido es el de columnas, y el contenedor es la lista de campos. Cada fila es un elemento con su propio borde, algo que con display: contents sería imposible.
<ul class="formulario">
<li class="campo">
<label for="nombre">Nombre</label>
<input id="nombre" name="nombre">
</li>
<li class="campo">
<label for="direccion">Dirección de facturación</label>
<input id="direccion" name="direccion">
</li>
<li class="campo">
<label for="tel">Teléfono</label>
<input id="tel" name="tel">
</li>
</ul>
.formulario {
display: grid;
grid-template-columns: max-content 1fr; /* etiqueta y campo */
gap: 0.75rem 1rem;
list-style: none;
margin: 0;
padding: 0;
}
.campo {
grid-column: span 2;
display: grid;
grid-template-columns: subgrid;
align-items: baseline;
padding-block: 0.5rem;
border-block-end: 1px solid #313244;
}
La columna de etiquetas mide max-content, es decir, lo que ocupe la etiqueta más larga de todas las filas, porque las tres comparten la pista. Sin subgrid, cada .campo tendría su propia columna dimensionada por su propia etiqueta y los campos empezarían a anchos distintos.
Este es el ejemplo que la propia especificación usa para motivar subgrid, y con razón: es el caso donde la alternativa —una tabla— destroza la semántica, y la otra alternativa —display: contents en .campo— elimina la caja que necesitas para el borde.
El contenido alineado con la rejilla de la página
Tercer caso. Una página con una rejilla global de tres bandas: margen, contenido y margen. Quieres que un artículo largo coloque sus párrafos en la banda central y que ciertos elementos —una imagen, una cita, un bloque de código— rompan a ancho completo.
.pagina {
display: grid;
grid-template-columns:
[total-inicio] minmax(1rem, 1fr)
[texto-inicio] min(65ch, 100%)
[texto-fin] minmax(1rem, 1fr)
[total-fin];
}
.articulo {
grid-column: total-inicio / total-fin;
display: grid;
grid-template-columns: subgrid;
}
/* Los hijos del artículo se colocan usando los nombres del abuelo */
.articulo > * { grid-column: texto-inicio / texto-fin; }
.articulo > figure { grid-column: total-inicio / total-fin; }
.articulo > .cita-ancha { grid-column: total-inicio / total-fin; }
Sin subgrid, el artículo tendría que redefinir sus propias columnas replicando las medidas de la página, y esas medidas se desincronizarían en el primer rediseño. Con subgrid, el artículo hereda las líneas y los nombres, y una regla de una línea coloca todo el contenido en la banda de texto.
El patrón escala bien: puedes añadir más bandas a la rejilla de página —una para notas al margen, otra para elementos que sobresalen un poco— y el artículo las hereda sin cambiar nada.
Cuando el número de elementos internos varía
Los tres ejemplos anteriores asumen un número fijo de elementos por tarjeta o por fila. Cuando no lo es, hay dos estrategias.
La primera: normalizar el marcado. Si algunas tarjetas no tienen descripción, emite un elemento vacío en su lugar. Es la solución más simple y la que mejor funciona, porque el subgrid necesita que la correspondencia entre pistas y elementos sea la misma en todas.
<p class="desc"></p> <!-- vacío pero presente -->
La segunda: agrupar lo variable en una sola pista. Declara pistas solo para lo que sí es constante y mete el resto en una pista que crezca.
.planes { grid-template-rows: auto 1fr auto; } /* título, cuerpo variable, cta */
.plan { grid-row: span 3; display: grid; grid-template-rows: subgrid; }
Con 1fr en la pista del cuerpo, todo el contenido variable vive en la segunda fila y solo se alinean el título y el botón. Es menos alineación, pero es robusta ante marcado heterogéneo, que suele ser lo que hay cuando el contenido viene de un gestor.
Si comparas la versión con subgrid de estos tres ejemplos con la versión anterior, la diferencia en CSS es de dos o tres líneas. La diferencia real está en el HTML que no aparece. En la versión sin subgrid, el formulario necesitaría una tabla o un aplanamiento; las tarjetas necesitarían contenedores adicionales para empujar el botón; el artículo necesitaría un contenedor por cada cambio de anchura, lo que en un cuerpo de texto largo significa fragmentar el contenido en decenas de divisiones que no significan nada. Ese marcado no es solo feo: es marcado que hay que generar, lo que implica que el sistema de contenidos, el editor, la plantilla y quien escribe el texto tienen que conocer necesidades de presentación. Es la fuga de abstracción más cara de todo el desarrollo web, y lleva ocurriendo tanto tiempo que casi nadie la percibe como un problema. Subgrid es una de las pocas funcionalidades que la reduce en lugar de aumentarla, y por eso su impacto real se mide en un sitio poco intuitivo: no en el CSS que escribes, sino en lo cerca que puede quedarse tu HTML de lo que el documento significa. Cuando evalúes si merece la pena adoptar algo nuevo, esa es la métrica que mejor predice si vas a seguir contento con la decisión dentro de tres años.