🔎 Buscar

🎨 CSS moderno

Flexbox, Grid, custom properties, unidades modernas, animaciones, media y container queries, y BEM, con layouts reales completos.

Wiki / Apuntes📖 Contenido

CSS moderno

CSS dejó de ser el lenguaje de “decorar cajas” hace años. Con Flexbox, Grid, custom properties y container queries puedes construir layouts complejos, sistemas de diseño y temas dinámicos con poco código y sin frameworks. Este artículo te da la base real, con ejemplos que puedes copiar y adaptar.

💡 Los frameworks de CSS (Tailwind, Bootstrap) no te eximen de entender estos conceptos: son una abstracción sobre los mismos primitivos. Saber la teoría te permite depurar lo que generan.


Flexbox

Flexbox reparte el espacio en una dimensión (una fila o una columna). Es ideal para alinear contenido y distribuir espacio.

El contenedor

.container {
  display: flex;           /* activa flex */
  flex-direction: row;     /* row (default) | column | row-reverse | column-reverse */
  flex-wrap: wrap;         /* nowrap | wrap | wrap-reverse */
  justify-content: center; /* main axis: flex-start | flex-end | center | space-between | space-around | space-evenly */
  align-items: center;     /* cross axis: stretch | flex-start | flex-end | center | baseline */
  gap: 16px;               /* espacio entre ítems (main y cross) */
}
<div class="toolbar">
  <button>Atrás</button>
  <button>Editar</button>
  <button>Guardar</button>
</div>
.toolbar {
  display: flex;
  justify-content: space-between; /* pega los extremos */
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
}

Los ítems: flex-grow, flex-shrink, flex-basis

  • flex-basis: tamaño base antes de repartir espacio.
  • flex-grow: cuánto crece respecto a los demás cuando sobra espacio (0 = no crece).
  • flex-shrink: cuánto se encoge cuando falta espacio.
flex: <grow> <shrink> <basis> Efecto
flex: 1 Crece igual que los demás (1 1 0%)
flex: 0 0 auto Tamaño natural, no crece ni encoge
flex: 1 1 auto Base según contenido, reparte el sobrante
flex: 0 1 200px Máximo 200px base, se encoge si falta
.sidebar  { flex: 0 0 240px; }  /* fija 240px */
.content  { flex: 1; }          /* ocupa todo lo que quede */

align-self anula align-items en un ítem concreto. margin: auto en un ítem flex absorbe el espacio sobrante (centrado “clásico” sin flex extra).


Grid

Grid trabaja en dos dimensiones (filas y columnas) a la vez. Es el layout de página, tarjetas y galerías.

Definir las pistas

.layout {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;   /* 3 columnas proporcionales */
  grid-template-rows: auto 1fr auto;    /* header, contenido, footer */
  gap: 24px;                            /* separación entre celdas */
}
Función Qué hace
fr Fracción del espacio disponible (1fr 2fr = 1/3 y 2/3)
repeat(3, 1fr) Tres columnas iguales
minmax(200px, 1fr) Mínimo 200px, crece hasta 1fr
auto-fit / auto-fill Columnas automáticas según el ancho (ver responsive)
auto Tamaño según el contenido

Grid areas para layouts legibles

.page {
  display: grid;
  grid-template-columns: 220px 1fr;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 16px;
  min-height: 100vh;
}
.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

Alineación y posicionamiento

.card {
  display: grid;
  justify-items: center;   /* eje inline (columna) */
  align-items: center;     /* eje de bloque (fila) */
  place-items: center;     /* atajo para ambos */
}

.item {
  justify-self: end;       /* posición de un ítem concreto */
  grid-column: 1 / -1;     /* ocupar toda la fila */
  grid-row: 2 / 4;         /* de la fila 2 a la 4 */
}

Custom properties (variables CSS)

Las variables se definen con -- y se leen con var(). Viven en la cascada, así que puedes redefinirlas por contexto (temas, componentes, estados).

:root {
  --color-bg: #ffffff;
  --color-text: #1a1a1a;
  --color-primary: #2563eb;
  --radius: 8px;
  --space: 16px;
}

.button {
  background: var(--color-primary);
  border-radius: var(--radius);
  padding: var(--space);
}

Dark mode con prefers-color-scheme

El truco más potente de las variables: cambiar el tema redefiniendo solo las variables.

:root {
  --bg: #ffffff;
  --text: #111111;
  --surface: #f4f4f5;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #111111;
    --text: #f4f4f5;
    --surface: #1f1f23;
  }
}

body {
  background: var(--bg);
  color: var(--text);
}
.card {
  background: var(--surface);
}

💡 Un sistema de temas serio se construye sobre variables. Cambias --color-primary en un contenedor y toda la jerarquía dentro se re-tintea sin tocar cada componente.


Unidades modernas

Unidad Relativa a Uso típico
rem font-size de la raíz (html) Tipografía y espaciado accesibles
em font-size del elemento padre Padding de un botón
vh / vw 1% del viewport alto/ancho Héroes, overlays
dvh / svh Viewport dinámico/pequeño Evitar saltos en móvil
% Contenedor padre Anchos fluidos
clamp() Min, preferido, max Tipografía fluida
ch Ancho del 0 Límite de ancho de texto legible
/* Tipografía fluida: mínimo 1rem, ideal 2vw, máximo 2.5rem */
h1 { font-size: clamp(1rem, 2vw + 1rem, 2.5rem); }

/* Ajustar al viewport dinámico del móvil (sin la barra del navegador) */
.hero { height: 100dvh; }

/* Ancho de línea legible (~65 caracteres) */
.article { max-width: 65ch; }

⚠️ Evita px para tamaño de fuente: rompe el ajuste de zoom del navegador y la accesibilidad. Usa rem para que el usuario pueda escalar.


Centrar: las tres formas

1. Flexbox (caja única, contenido variable)

.center-flex {
  display: flex;
  justify-content: center;
  align-items: center;
}

2. Grid con place-items (mismo resultado, una línea)

.center-grid {
  display: grid;
  place-items: center;
}

3. Margin auto (solo horizontal, en bloque)

.block {
  width: min(90%, 600px);
  margin-inline: auto;   /* centra horizontalmente */
}

💡 Para centrar un elemento absoluto: position: absolute; inset: 0; margin: auto; con dimensiones fijas. Para texto: text-align: center + line-height igual a la altura del contenedor.


Animaciones

transition vs animation

transition @keyframes
Cuándo Cambio de estado (hover, clase) Animación independiente, continua o con pasos
Control Inicio/fin implícitos Pasos definidos (fromto o 0%100%)
Repetición No (solo al cambiar) Sí, con infinite
/* transition: suaviza el cambio de un estado a otro */
.button {
  background: var(--color-primary);
  transition: background 0.2s ease, transform 0.2s ease;
}
.button:hover {
  background: #1d4ed8;
  transform: translateY(-2px);
}

/* animation con @keyframes */
@keyframes slide-in {
  from { transform: translateX(100%); opacity: 0; }
  to   { transform: translateX(0);    opacity: 1; }
}
.toast {
  animation: slide-in 0.3s ease-out forwards;
}

Timing functions y buenas prácticas

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.05); }
}
.spinner { animation: pulse 1.5s ease-in-out infinite; }
  • Anima transform y opacity (lo que usa el compositor), no width, left o top (repintado y layout caros).
  • Respeta prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

Media queries y container queries

Media queries (viewport)

/* Móvil primero: lo básico sin media query, luego refinar hacia arriba */
.card-grid { display: grid; gap: 16px; grid-template-columns: 1fr; }

@media (min-width: 640px)  { .card-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .card-grid { grid-template-columns: repeat(4, 1fr); } }

💡 Regla de dedo: escribe mobile-first (min-width) para que lo complejo se aplique solo en pantallas grandes. max-width te obliga a sobreescribir constantemente.

auto-fit y minmax: responsive sin media queries

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

Las tarjetas se recolocan solas según el ancho disponible, sin una sola media query.

Container queries (contexto, no viewport)

Con @container el elemento responde al tamaño de su contenedor, no de la pantalla. Es lo correcto para componentes reutilizables que viven en distintos anchos.

.card-list {
  container-type: inline-size;   /* el contenedor define el contexto */
}

@container (min-width: 400px) {
  .card { grid-template-columns: 1fr 1fr; }
}

BEM (Block Element Modifier)

BEM es una convención de nombrado que da estructura a tus clases y evita conflictos de especificidad:

  • Block: componente autónomo → .card
  • Element: parte del block → .card__title
  • Modifier: variante o estado → .card--featured
<article class="card card--featured">
  <h2 class="card__title">Título</h2>
  <p class="card__body">Texto del cuerpo de la tarjeta.</p>
  <button class="card__button card__button--primary">Acción</button>
</article>
.card { display: flex; flex-direction: column; gap: 12px; padding: 16px; }
.card--featured { border: 2px solid var(--color-primary); }
.card__title { font-size: 1.25rem; }
.card__button { padding: 8px 16px; }
.card__button--primary { background: var(--color-primary); color: #fff; }

⚠️ BEM evita que un componente estropee a otro. No anides bloques en el selector (.card .card__title) ni abuses de !important: la especificidad baja con BEM se gestiona con orden y nombres.


Layouts reales completos

<nav class="navbar">
  <a class="navbar__logo" href="/">Logo</a>
  <ul class="navbar__links">
    <li><a href="/">Inicio</a></li>
    <li><a href="/blog">Blog</a></li>
    <li><a href="/about">Sobre mí</a></li>
  </ul>
  <button class="navbar__cta">Login</button>
</nav>
.navbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 12px 24px;
}
.navbar__links {
  display: flex;
  gap: 16px;
  list-style: none;
  margin: 0;
}
@media (max-width: 640px) {
  .navbar__links { display: none; }   /* en móvil, ocultar (o un menú hamburguesa) */
}

Card grid (galería de productos)

.products {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 20px;
}
.product {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.product__image { aspect-ratio: 1 / 1; object-fit: cover; width: 100%; }
.product__body  { display: flex; flex-direction: column; gap: 8px; padding: 16px; flex: 1; }
.product__price { margin-top: auto; font-weight: 700; }   /* empuja el precio abajo */

Hero a pantalla completa

.hero {
  display: grid;
  place-items: center;
  min-height: 100dvh;
  background: linear-gradient(135deg, #2563eb, #7c3aed);
  color: #fff;
  text-align: center;
  padding: 24px;
}
.hero__title { font-size: clamp(1.8rem, 4vw, 3.5rem); margin: 0 0 16px; }
.hero__subtitle { max-width: 60ch; margin: 0 auto 24px; }

Página completa con grid areas

.page {
  display: grid;
  grid-template-areas:
    "header header"
    "sidebar content"
    "footer footer";
  grid-template-columns: 240px 1fr;
  grid-template-rows: auto 1fr auto;
  gap: 20px;
  min-height: 100vh;
}
@media (max-width: 768px) {
  .page {
    grid-template-areas:
      "header"
      "content"
      "sidebar"
      "footer";
    grid-template-columns: 1fr;
  }
}

Para profundizar

Estudio · Recursos de todo el mundo (inglés, chino, japonés, español, francés, ruso…) curados y traducidos al español.