🎨 CSS moderno
Flexbox, Grid, custom properties, unidades modernas, animaciones, media y container queries, y BEM, con layouts reales completos.
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-primaryen 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
pxpara tamaño de fuente: rompe el ajuste de zoom del navegador y la accesibilidad. Usarempara 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-heightigual 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 (from→to 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,leftotop(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-widthte 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
Navbar responsive
<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
- MDN — CSS: la referencia completa, con artículos de Flexbox y Grid.
- CSS-Tricks — A Complete Guide to Flexbox: la guía visual de referencia.
- CSS-Tricks — A Complete Guide to Grid: lo mismo para Grid.
- Flexbox Froggy y Grid Garden: juegos para practicar.
- caniuse: compatibilidad de cada propiedad en navegadores.