Actualizado 15 de julio de 2026

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.

La vista de rejilla de Scale Composer junto a su exportación CSS generada: tres niveles de breakpoint como custom properties, más las clases de contenedor, rejilla y span

¿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.

Seguir leyendo

  • ¿Qué es una rejilla de 12 columnas?

    Una rejilla de 12 columnas divide la página en doce columnas iguales con medianiles fijos. Por qué gana el doce: mitades, tercios, cuartos y sextos caen todos en columnas enteras.

  • Spans de columnas y anidamiento

    Cómo funciona el span de columnas en rejilla: la gramática del span, patrones de página como 8+4 y 4+4+4, cómo elegir spans según contenido y alineación, y cómo anidar rejillas de forma limpia.