learning-front

Nivel 1 · HTML y CSS: la estructura y la piel

CSS Grid y custom properties

Layouts en dos dimensiones y variables CSS para un diseño consistente y mantenible: el grid del Team Builder que se adapta a cualquier pantalla sin una sola media query.

En el capítulo 8 escribiste colores y tamaños directamente en las reglas —y al final ya notabas el problema: el acento aparecía tres veces, un radio se duplicaba—. Ese capítulo terminó señalando que la solución nativa de CSS son las custom properties (variables). Este es el capítulo que paga esa promesa.

Junto con las custom properties aprenderás CSS Grid: el sistema que coloca los componentes en una cuadrícula de dos dimensiones. El box model y Flexbox (cap. 9) ya te dan el interior de cada carta; Grid organiza las cartas entre sí.

Seguimos con el Overwatch Team Builder. El HTML de las cartas ya está; ahora las colocamos en un grid responsive y convertimos los colores y espaciados en variables que se puedan cambiar en una línea.

Grid frente a Flexbox: no se sustituyen, se complementan#

En el capítulo de Flexbox viste cómo distribuir elementos a lo largo de un eje: el retrato y el nombre de la carta en una fila, las estadísticas en otra. Flexbox es unidimensional: trabaja en fila o en columna, pero no en los dos a la vez.

CSS Grid es bidimensional: define filas y columnas simultáneamente y coloca los ítems en celdas de esa cuadrícula. Si Flexbox organiza el interior de un componente, Grid organiza los componentes entre sí.

En la práctica se usan juntos: Grid para el layout de la página (el grid de cartas), Flexbox para el interior de cada carta (el retrato junto al nombre).

La cuadrícula básica#

css
.hero-grid {
  display: grid;
  /* tres columnas iguales */
  grid-template-columns: 1fr 1fr 1fr;
  /* espacio entre filas y columnas */
  gap: 1rem;
}

1fr significa “una fracción del espacio disponible”. Con tres 1fr el espacio se divide en tres partes iguales. Con 2fr 1fr la primera columna sería el doble de ancha que la segunda.

gap reemplaza el viejo grid-gap (que sigue funcionando, pero es legacy). Acepta dos valores: el primero para filas y el segundo para columnas. Con uno solo aplica a ambas.

repeat() y minmax(): el grid que se adapta solo#

Tres columnas fijas funcionan en escritorio. En una tablet de 600px quedan estrechas; en un móvil de 360px, aplastadas. La solución sin media queries:

css
.hero-grid {
  /* tantas columnas como quepan, cada una de 15rem como mínimo */
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}

Descompuesto:

  • repeat(auto-fill, ...) — crea tantas columnas como quepan en el contenedor.
  • minmax(15rem, 1fr) — cada columna mide al menos 15rem y puede crecer hasta llenar su fracción.

El resultado: en móvil cabe una columna; en tablet, dos; en escritorio, tres o más. El navegador hace el cálculo; tú no escribes ningún breakpoint.

auto-fill frente a auto-fit: la diferencia que se ve con pocas cartas#

auto-fill reserva columnas aunque estén vacías. Si en un contenedor de 60rem caben tres columnas de 15rem pero solo tienes dos héroes, la tercera columna existe pero está vacía: las dos cartas no llenan el ancho.

auto-fit colapsa las columnas vacías. Con dos héroes y espacio para tres, las dos cartas se estiran para llenar el ancho disponible.

css
/* auto-fill: reserva columnas vacías */
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));

/* auto-fit: colapsa lo que no tiene contenido */
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));

Cuál usar depende del diseño. En una galería donde la alineación con otros elementos importa, auto-fill mantiene el ritmo aunque falten ítems. En el Team Builder, donde las cartas deben llenar el espacio, auto-fit es más natural.

Centrar en una celda: place-items#

Grid trae un atajo cómodo para centrar el contenido de una celda en los dos ejes a la vez: place-items, que combina align-items (eje vertical) y justify-items (eje horizontal) en una sola línea.

css
.celda {
  display: grid;
  /* centrado vertical y horizontal */
  place-items: center;
}

Es la forma más corta de centrar algo dentro de su caja, y por eso la verás mucho: por ejemplo, para centrar las iniciales dentro del retrato circular de un héroe.

Áreas con nombre: grid-template-areas#

Para layouts de página con regiones nombradas (cabecera, barra lateral, contenido, pie), Grid ofrece una sintaxis que “dibuja” la estructura.

Igual que grid-template-columns define el tamaño de las columnas, existe grid-template-rows para las filas. Funciona con los mismos valores: auto deja que la fila mida lo que ocupa su contenido; 1fr le cede el espacio sobrante.

css
.layout {
  display: grid;
  /* fila 1: lo que ocupe la cabecera; fila 2: ocupa el resto; fila 3: lo que ocupe el pie */
  grid-template-rows: auto 1fr auto;
}

Con esa base, grid-template-areas añade los nombres:

css
.layout {
  /* activa CSS Grid en este contenedor */
  display: grid;
  /* columna fija de 16rem + columna que ocupa el resto */
  grid-template-columns: 16rem 1fr;
  /* tres filas: cabecera / contenido / pie */
  grid-template-rows: auto 1fr auto;
  /* "dibuja" el layout con nombres de zona */
  grid-template-areas:
    /* cabecera ocupa las dos columnas */
    "header  header"
    /* barra lateral + contenido principal */
    "sidebar main"
    /* pie ocupa las dos columnas */
    "footer  footer";
}

/* coloca este elemento en la zona "header" */
.site-header { grid-area: header; }
/* coloca este elemento en la zona "sidebar" */
.sidebar     { grid-area: sidebar; }
/* coloca este elemento en la zona "main" */
.main        { grid-area: main; }
/* coloca este elemento en la zona "footer" */
.site-footer { grid-area: footer; }

El CSS “habla”: quien lo lee ve el layout sin necesidad de interpretar coordenadas. Para reorganizarlo basta con redefinir grid-template-areas y los elementos se recolocan solos, sin tocar coordenadas. No lo usaremos en el ejercicio de este capítulo —el grid de cartas es más simple—, pero es la base de cualquier layout de página con regiones nombradas.

Custom properties: variables que vive en CSS#

Una custom property es una variable nativa de CSS. Se declara con --nombre y se usa con var(--nombre):

css
/* :root es la raíz del documento; las variables aquí son globales */
:root {
  /* custom property: el color de acento de la marca */
  --color-acento: #b8336a;
  /* radio de esquinas de las tarjetas, en un único sitio */
  --radio-tarjeta: 12px;
}

.hero-portrait {
  /* usa el valor de la custom property */
  background: var(--color-acento);
}

.hero-card {
  /* si el radio cambia, solo tocas :root */
  border-radius: var(--radio-tarjeta);
}

:root es el selector de la raíz del documento (equivale a html pero con mayor especificidad). Declarar las variables ahí las hace disponibles en toda la página.

¿Por qué es mejor que repetir #b8336a en cada regla? Porque hay un único sitio que tocar. Si el color de acento cambia, cambias la variable y toda la página se actualiza. Con el valor literal disperso en diez reglas, cambiar de marca significa buscar y reemplazar con riesgo de olvidarse alguno.

Las custom properties también se pueden cambiar desde JavaScript en tiempo de ejecución: con una sola instrucción (document.documentElement.style.setProperty) reasignas el valor de una variable y toda la página que la use se actualiza al instante, sin recargar. Eso es lo que hace posible el cambio de tema de color —claro/oscuro— con un botón. No te preocupes ahora por esa sintaxis: es JavaScript y lo verás en el Nivel 2; aquí lo que importa es entender por qué se puede hacer tan fácil: porque el color vive en un único sitio, la variable.

Pruébalo#

El playground muestra el grid de cartas con auto-fit y minmax. Hay una variable --grid-col-min que controla el ancho mínimo de cada columna. Cámbiala de 12rem a 20rem y observa cómo el grid pasa de cuatro columnas a dos sin tocar ninguna otra regla. Eso es una palanca de diseño: un único punto que cambia el comportamiento en cascada.

Comprueba lo que sabes#

Pregunta 1 de 5

Tienes un grid con `grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr))` y solo hay 2 cartas en un contenedor de 60rem. ¿Qué ocurre con la tercera columna que "cabría"?

Tu turno#

El HTML ya está dado. Tu trabajo es el CSS: hacer funcionar el grid, la cabecera y las cartas. Abre styles.css en el playground y sigue los TODOs del fichero de partida. Cuando creas que lo tienes, despliega las soluciones y compáralas con la tuya.

Ejercicio · en esta página

Dale estilo al grid del Team Builder

El HTML de la página ya está hecho y no debes tocarlo. Tu trabajo es completar styles.css siguiendo los TODOs: base, cabecera, grid de héroes y cartas. El objetivo no es que "quede bonito" —eso lo decides tú—, sino que el grid sea responsive sin media queries y que los valores repetidos (colores, espaciados) vivan en custom properties.

Paso 1: Que funcione

  • El grid muestra las cartas en columnas visibles.
  • Las cartas tienen fondo, borde y padding reconocibles.
  • La cabecera muestra el logotipo y la navegación en la misma fila.
Ver soluciones
/* ==========================================================================
   NIVEL OK — "funciona y se ve"
   ==========================================================================
   El grid muestra las cartas en tres columnas fijas. Cada color, espacio o
   tamaño de fuente está escrito a pelo donde lo necesita, sin ningún sistema
   que lo agrupe. Se ve bien; si mañana quieres cambiar el color de acento o
   el espaciado base tienes que rastrear el fichero entero y cambiar a mano
   en diez sitios distintos. Es el punto de partida, no el de llegada.
   ========================================================================== */

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: #1c1b22;
  background: #f7f6fb;
  line-height: 1.5;
}

.page {
  max-width: 60rem;
  margin: 0 auto;
  padding: 2.5rem 1.5rem 4rem;
}

/* --- Cabecera ----------------------------------------------------------- */

.site-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid #e6e3ee;
  margin-bottom: 2rem;
}

.brand {
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0;
}

.brand span {
  color: #b8336a;
}

.site-nav a {
  color: #5b5966;
  text-decoration: none;
  font-size: 0.9rem;
  margin-left: 1rem;
}

.site-nav a:hover {
  color: #b8336a;
}

/* --- Título de sección -------------------------------------------------- */

.section-title {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #5b5966;
  margin: 0 0 1rem;
}

/* --- Grid de héroes ----------------------------------------------------- */

/* Tres columnas fijas iguales. Funciona en pantallas anchas; en móvil las
   columnas se quedan estrechas y el texto puede cortarse. Para hacerlo
   responsive sin media queries necesitas minmax() y auto-fill: eso viene en
   el nivel Mejor. */
.hero-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* --- Carta de héroe ----------------------------------------------------- */

.hero-card {
  background: #ffffff;
  border: 1px solid #e6e3ee;
  border-radius: 12px;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.hero-top {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}

.hero-portrait {
  flex: none;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: 0.85rem;
  color: #ffffff;
  background: #b8336a;
}

.hero-name {
  font-size: 1.05rem;
  font-weight: 700;
  margin: 0;
}

.hero-role {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #5b5966;
  margin: 0.1rem 0 0;
}

.hero-stats {
  display: flex;
  gap: 1.25rem;
  margin: 0;
  padding-top: 0.5rem;
  border-top: 1px solid #e6e3ee;
}

.stat {
  display: flex;
  flex-direction: column;
}

.stat dt {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #5b5966;
  margin: 0;
}

.stat dd {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 0.15rem 0 0;
}

/* --- Pie ---------------------------------------------------------------- */

.site-footer {
  margin-top: 3rem;
  padding-top: 1.25rem;
  border-top: 1px solid #e6e3ee;
  color: #5b5966;
  font-size: 0.85rem;
}

Por qué este nivel

  • El grid funciona con tres columnas fijas (1fr 1fr 1fr): se ve bien en escritorio.
  • En pantallas estrechas las columnas se quedan cortas y el texto puede cortarse; no hay ningún mecanismo que lo evite.
  • Colores y espaciados aparecen repetidos a lo largo del fichero. Si el diseñador cambia el color de acento, hay que rastrear el fichero entero y tocar en diez sitios.
  • Es el punto de partida válido: funciona y se puede entregar. Pero escalar desde aquí es costoso.