Construir la rejilla en CSS puro
Una página construida sobre un framework de la época de 2013 anunciaba su
maquetación en cada etiqueta: class="col-xs-12 col-sm-6 col-md-4" en
tarjeta tras tarjeta, una clase por elemento por breakpoint, con una hoja
de estilos del framework acompañando para que las clases significaran
algo. El equivalente moderno de todo ese aparato son unas veinte líneas de
CSS que puedes leer de una sentada.
Un sistema de rejilla CSS en CSS moderno es un conjunto de custom properties que llevan las decisiones por breakpoint —número de columnas, medianil, margen— vueltas a declarar dentro de media queries, más tres clases que las consumen: un contenedor que limita y centra la página, una rejilla que dispone las columnas y clases de span que permiten a los elementos reclamar anchos de columna. Sin dependencia de framework, sin paso de compilación; el propio motor de maquetación del navegador hace el trabajo que el framework solía simular.
Este artículo recorre ese código línea por línea: la capa de variables,
las tres clases, el truco engañoso de minmax(0, 1fr) y el truco de
recorte de una sola línea que sustituye la sopa de clases por breakpoint.
Es la mitad de implementación del tema de la
rejilla de maquetación; qué deben ser las
columnas, los medianiles y los breakpoints es tarea del resto del grupo.
¿Cómo se divide la rejilla entre variables y clases?
La arquitectura tiene exactamente dos capas, y la división es lo importante.
Las variables son las decisiones de diseño. --grid-cols,
--grid-gutter y --grid-margin guardan los valores de un nivel a la
vez: 4 / 16px / 16px en móviles, vueltos a declarar a 8 / 24px / 29px
desde 768px y 12 / 24px / 66px desde 1200px. --container guarda el
límite global.
Esos pocos números por nivel
son toda la política de maquetación: todo lo que un equipo de verdad
decide cuando decide «la rejilla».
Las clases son la mecánica. .grid-container, .grid y los spans
.col-N no contienen ningún número propio; solo leen las variables. La
mecánica se escribe una vez y rara vez se vuelve a tocar: un rediseño o un
nuevo breakpoint solo cambia los bloques :root.
Si trabajas con design tokens, el paralelismo es exacto: las variables son tokens de maquetación en tiempo de ejecución. Los mismos valores de columnas/medianil/margen que viven en la sección de maquetación de un archivo de tokens se convierten aquí en custom properties, y las clases desempeñan el papel que los componentes desempeñan en todas partes en un sistema de tokens: consumidores de decisiones, nunca dueños de ellas.
¿Qué aspecto tiene el CSS completo de la rejilla?
Esta es la forma de la exportación CSS de Scale Composer para su rejilla por defecto, anotada:
/* Tokens de rejilla
* Ancho máximo global: 1200px
* Móvil: ≥0px → 4 cols · medianil 16px · margen 16px
* Tablet: ≥768px → 8 cols · medianil 24px · margen 29px
* Escritorio: ≥1200px → 12 cols · medianil 24px · margen 66px
*/
:root { /* el nivel móvil es el valor por defecto */
--container: 1200px;
--grid-cols: 4;
--grid-gutter: 16px;
--grid-margin: 16px;
}
@media (min-width: 768px) { /* tablet vuelve a declarar las decisiones */
:root {
--grid-cols: 8;
--grid-gutter: 24px;
--grid-margin: 29px;
}
}
@media (min-width: 1200px) { /* escritorio las vuelve a declarar */
:root {
--grid-cols: 12;
--grid-gutter: 24px;
--grid-margin: 66px;
}
}
.grid-container { /* limita y centra la página */
max-width: var(--container);
margin-inline: auto;
padding-inline: var(--grid-margin);
}
.grid { /* dispone las columnas */
display: grid;
grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
gap: var(--grid-gutter);
}
/* Spans — recortados al número de columnas del nivel actual */
.col-1 { grid-column: span min(1, var(--grid-cols)); }
.col-2 { grid-column: span min(2, var(--grid-cols)); }
/* … .col-3 a .col-11 siguen el mismo patrón … */
.col-12 { grid-column: span min(12, var(--grid-cols)); }
Las tres clases reparten el trabajo con claridad. .grid-container es
dueña del marco de la página: max-width la limita, margin-inline: auto
la centra, padding-inline mantiene el contenido alejado del borde del
viewport según el margen del nivel. .grid es dueña de las columnas:
repeat(var(--grid-cols), minmax(0, 1fr)) crea pistas fluidas iguales y
gap coloca el medianil entre ellas; la
mecánica básica de CSS Grid
se encarga de toda la aritmética. Las clases .col-N no poseen nada más
que una reclamación: abarcar N columnas,
sean lo que sean las columnas en cada momento.
Abre esta exportación, anotada, en Scale Composer — la vista de rejilla genera el bloque de arriba a partir de sus ajustes por breakpoint, con cada medianil y margen ajustado a la escala de espaciado compartida.

¿Por qué minmax(0, 1fr) en lugar de 1fr a secas?
Porque 1fr por sí solo tiene un suelo. Es la forma abreviada de
minmax(auto, 1fr), y auto significa «al menos el tamaño mínimo de este
contenido». Mete una URL larga sin cortes, una tabla ancha o un bloque
<pre> en una columna de una rejilla repeat(12, 1fr) y esa columna
crece sin hacer ruido más allá de lo que le corresponde; las otras once se
encogen para hacer sitio, y la promesa de columnas iguales se rompe en
cualquier fila que contenga el contenido más ancho. Es una de las
sorpresas de depuración más habituales en Grid precisamente porque nada da
error: la maquetación simplemente está mal, y solo en las filas con
contenido incómodo.
minmax(0, 1fr) elimina el suelo. Cada pista es exactamente una fracción
del espacio libre, siempre; el contenido más ancho que su columna se
desborda de la columna —de forma visible y corregible— en lugar de doblar
toda la rejilla en torno a sí mismo. Pistas honestas, desbordamiento
honesto.
¿Cómo sobreviven los spans a los breakpoints más pequeños?
Esta línea es donde de verdad termina la era de la sopa de clases:
.col-8 { grid-column: span min(8, var(--grid-cols)); }
Un span 8 ingenuo se comporta mal en los móviles: el nivel móvil tiene
cuatro columnas explícitas, y Grid honra un span sobredimensionado creando
pistas implícitas: la rejilla de cuatro columnas se convierte sin avisar
en una de ocho, y todo lo demás se encoge y se desalinea. Envolver el span
en min(N, var(--grid-cols)) reevalúa en cambio la reclamación por nivel:
un elemento .col-8 ocupa ocho de doce columnas en escritorio, las ocho
en tablet, y se recorta a las cuatro en un móvil —ancho completo, que es
lo que significa apilar—. Ni una segunda clase, ni una media query extra.
La era de los frameworks resolvía el mismo problema con clases por nivel
(col-md-8 col-sm-4): una decisión por elemento por breakpoint,
codificada en el marcado. El recorte sustituye esa taxonomía por un valor
por defecto; y donde el ajuste por defecto no es el diseño que quieres, un
span vuelto a declarar dentro de una media query es ahora la excepción y
no el sistema.
¿Qué aspecto tiene una página construida sobre él?
Una página de tres secciones —hero, artículo con barra lateral, fila de tarjetas— es marcado más reclamaciones:
<body class="grid-container">
<header class="grid">
<h1 class="col-12">Hero — full width at every tier</h1>
</header>
<main class="grid">
<article class="col-8">Article body</article>
<aside class="col-4">Sidebar</aside>
</main>
<section class="grid">
<div class="col-4">Card</div>
<div class="col-4">Card</div>
<div class="col-4">Card</div>
</section>
</body>
En el nivel de escritorio los números salen así: el contenedor se limita a 1200px, los márgenes se llevan 2 × 66px, los once medianiles se llevan 11 × 24px = 264px, y cada columna obtiene (1200 − 132 − 264) ÷ 12 = 67px. El artículo abarca 8 × 67 + 7 × 24 = 704px, la barra lateral y cada tarjeta 340px. En un móvil de 375px cada uno de esos spans se recorta a cuatro columnas y la página se convierte en una pila limpia de una sola columna: no se escribió nada extra para que eso ocurriera.
¿Qué no puede hacer una rejilla de veinte líneas?
Posiciona cajas sobre un único conjunto compartido de columnas, y eso es
todo lo que hace. No tiene respuesta para alinear los interiores de
tarjetas hermanas entre sí (eso es trabajo de subgrid), ni empaquetado
tipo masonry, ni áreas con nombre, ni ingenio de colocación automática.
Nada de eso es un defecto del patrón; marca el punto en el que dejas de
usar el sistema y empiezas a usar CSS Grid directamente:
grid-template-areas o subgrid en el componente concreto que lo
necesite. El sistema cubre la página; el propio Grid cubre los casos
especiales.
Exporta una rejilla con tus propios números
La forma más rápida de hacer tuyo este patrón es generarlo a partir de decisiones que hayas tomado tú mismo. Ajusta las columnas, los medianiles y los márgenes por breakpoint en la vista de rejilla —cada medida avanza por la misma escala de espaciado de la que bebe el resto del sistema— y luego lee la hoja de estilos que resulta: exporta el CSS de la rejilla con tus propios valores de breakpoint en Scale Composer.