Grid Workshop
Subgrid alignment
Demo de CSS subgrid: los títulos, metadatos y footers de las cards quedan alineados en filas comunes aunque el texto tenga alturas distintas. Activá o desactivá el subgrid para comparar.
Subgrid
Con subgrid
The Shadow: la caída del último bastión del norte
The Shadow
Cómo funciona y cómo implementarlo
Cómo funciona
Grid padre —
.cards-grid es un grid con filas explícitas; cada card es un grid item que ocupa su celda.
Subgrid anidado —
cada
.sub-card hace display:grid; grid-template-rows: subgrid; y grid-row: span N, delegando sus filas en las del padre. Así títulos, metadatos y footers comparten las mismas líneas.
Sin subgrid —
al quitar
subgrid, cada card crece por su propio contenido y los footers quedan a alturas distintas.
Toggle —
initSubgrid() alterna la clase no-subgrid en el grid y actualiza el badge y el aria-pressed.
Implementación
1. Marcado —
cada card debe tener las mismas secciones en el mismo orden:
portada · título · meta · footer.
2. CSS —
.cards-grid { display:grid; grid-template-columns:repeat(3,1fr); grid-template-rows: repeat(N, auto); } y .sub-card { display:grid; grid-template-rows:subgrid; grid-row:span N; }
3. Anclar el footer —
el
.sub-card__footer se coloca en la última fila del subgrid; al delegar en el padre, queda alineado con los demás.
4. Fallback —
si el navegador no soporta
subgrid, la card vuelve a su grid propio (grid-template-rows normal) y solo pierde la alineación compartida.
Código para replicar
Snippets autocontenidos: copiá cada bloque y pegá en tu proyecto. Las cards deben compartir las mismas secciones en el mismo orden para que el subgrid las alinee.
HTML
<div class="cards-grid">
<article class="sub-card">
<div class="sub-card__cover">
<img src="001.jpg" alt="Portada">
</div>
<div class="sub-card__text">
<h3>The Shadow</h3>
<p>Guion: W. Gibson<br>Arte: A. Reyes</p>
</div>
<div class="sub-card__footer">
<span class="sub-card__price">$12.990</span>
</div>
</article>
<!-- cards con distinta altura de texto… -->
</div>
CSS
:root{
--surface:hsl(215,11%,3%);
--border:hsl(214,35%,22%);
--text:hsl(215,25%,85%);
}
.cards-grid{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:12px;
align-items:start;
}
.sub-card{
display:grid;
grid-row:span 4;
grid-template-rows:subgrid;
background:var(--surface);
border:1px solid var(--border);
border-radius:0.625rem;
overflow:hidden;
}
.sub-card__footer{
margin-top:12px;
padding-top:10px;
border-top:1px solid var(--border);
}
/* Fallback sin subgrid */
.cards-grid.no-subgrid{
align-items:stretch;
}
.cards-grid.no-subgrid .sub-card{
display:flex;
flex-direction:column;
grid-row:unset;
grid-template-rows:unset;
}
.cards-grid.no-subgrid .sub-card__footer{
margin-top:auto;
}
JS
const grid = document.querySelector('.cards-grid');
const btn = document.querySelector('.subgrid-toggle');
const badge = document.querySelector('.subgrid-badge');
btn.addEventListener('click', () => {
const on = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', String(!on));
grid.classList.toggle('no-subgrid', on);
badge.textContent = on ? 'Sin subgrid — flex column' : 'Con subgrid';
});