Cómo crear una sección de blog con timeline vertical en React
Una sección de blog con timeline vertical en React coloca las tarjetas de artículos alternando a izquierda y derecha de una línea vertical centrada, deslizándose cada una desde su lado al hacer scroll mediante whileInView de Framer Motion. El layout usa un CSS Grid de tres columnas (tarjeta / conector / tarjeta), con la columna central que contiene el punto y la línea.
- Stack: React + Framer Motion + lucide-react, aproximadamente 97 líneas, sin dependencias adicionales.
- Motor de layout: CSS Grid inline con tres columnas, 1fr / 40px / 1fr, para alinear las tarjetas y el conector central.
- Entrada al hacer scroll: whileInView se activa una sola vez por tarjeta, con un desfase de 80 ms entre items y un margen de viewport de -50px.
- Accesible: la línea central es aria-hidden; las imágenes usan un alt vacío (decorativas). El componente es responsive y está probado en temas claro y oscuro.
- El primer punto de la timeline usa el color de acento como ancla visual; los puntos siguientes usan el color de borde para reducir el ruido visual.
Blog Timeline es una sección React que muestra una lista de artículos a lo largo de una línea central vertical, con tarjetas que se alternan a izquierda y derecha en cada fila. La entrada escalonada al hacer scroll da al contenido editorial una sensación de ritmo sin necesidad de bibliotecas complejas. Encaja igual de bien en sitios de agencia, publicaciones de medios y páginas de changelog.
Anatomía
La sección se construye en torno a un CSS Grid de tres columnas: una columna de contenido ancha a cada lado y una columna central estrecha de 40px para el conector. Una línea vertical de 2px recorre toda la altura del contenedor, posicionada en absoluto al 50%. Cada fila coloca su tarjeta de artículo en la columna 1 o en la 3 según su índice, mientras que la columna 2 contiene un punto de 14px centrado sobre la tarjeta. El primer punto usa el color de acento; los demás siguen el token de borde. Cada tarjeta contiene una imagen 16:9, una etiqueta de categoría, un título, un extracto, una insignia de fecha y un icono de flecha.
Cómo funciona
Cada fila de artículo es un motion.div con un estado inicial de `opacity: 0` y `x: -30` (tarjetas a la izquierda) o `x: 30` (tarjetas a la derecha). Cuando el elemento entra en el viewport, whileInView lo lleva a `opacity: 1, x: 0`. La transición usa un easing cubic-bezier personalizado `[0.16, 1, 0.3, 1]` con una duración de 600ms, y el retardo se calcula como `index * 0.08` segundos para crear el escalonado. El flag `once: true` garantiza que la animación se reproduzca una sola vez por tarjeta, evitando que se vuelva a disparar al volver a hacer scroll.
Cómo crearlo en React
Configurar el contenedor en grid de tres columnas
Envuelve cada fila de artículo en un div con `display: grid` y `gridTemplateColumns: '1fr 40px 1fr'`. Coloca una línea vertical de 2px en absoluto al 50% del contenedor para que pase por detrás de todas las filas. Esta única definición de grid gestiona la colocación de las tarjetas tanto a la izquierda como a la derecha.
<div style={{ position: "relative", maxWidth: "900px", margin: "0 auto" }}> {/* Center line */} <div aria-hidden style={{ position: "absolute", left: "50%", top: 0, bottom: 0, width: 2 }} /> {articles.map((article, i) => ( <div style={{ display: "grid", gridTemplateColumns: "1fr 40px 1fr" }} key={i}> {/* card + dot + spacer */} </div> ))} </div>Alternar los lados de las tarjetas con el índice
Deriva un booleano `isLeft` a partir de `i % 2 === 0`. Coloca la tarjeta en `gridColumn: isLeft ? '1' : '3'` y el espaciador vacío en la columna opuesta. El punto siempre se sitúa en la columna 2. Esto mantiene el orden del markup coherente sin importar en qué lado aparezca la tarjeta.
const isLeft = i % 2 === 0; // Card <div style={{ gridColumn: isLeft ? "1" : "3" }}>...</div> // Dot <div style={{ gridColumn: "2", display: "flex", justifyContent: "center" }}> <div style={{ width: 14, height: 14, borderRadius: "50%", background: i === 0 ? "var(--color-accent)" : "var(--color-border)" }} /> </div> // Spacer <div style={{ gridColumn: isLeft ? "3" : "1" }} />Añadir la entrada escalonada al hacer scroll
Convierte cada div de fila en un motion.div y ajusta el desplazamiento x inicial según isLeft. El retardo multiplicado por el índice crea la cascada. Mantén el margen de viewport en -50px para que la animación empiece antes de que el elemento entre por completo en pantalla, dando a la página una sensación de flujo continuo.
const EASE = [0.16, 1, 0.3, 1] as const; <motion.div initial={{ opacity: 0, x: isLeft ? -30 : 30 }} whileInView={{ opacity: 1, x: 0 }} viewport={{ once: true, margin: "-50px" }} transition={{ duration: 0.6, delay: i * 0.08, ease: EASE }} style={{ display: "grid", gridTemplateColumns: "1fr 40px 1fr" }} >Construir la tarjeta de artículo
Cada tarjeta es una etiqueta anchor simple con una imagen 16:9 en la parte superior, seguida de una zona de contenido con padding. La etiqueta de categoría usa el token de color de acento con uppercase y letter-spacing. La fecha y la flecha se sitúan en una flex row al final. Mantén la tarjeta como un único `<a>` para que toda la superficie sea clicable sin elementos interactivos anidados.
Cuándo usarlo
Recurre al layout de timeline cuando quieras presentar una serie de artículos con sentido de cronología o de progresión narrativa: páginas de índice de blog, changelogs de producto, listados de casos de estudio. Evítalo en páginas de catálogo planas donde todos los items tienen el mismo peso y la comparación importa más que la secuencia. En mobile las columnas alternadas colapsan en una sola columna, así que prueba el layout apilado antes de publicar.
Usado por
- Stripe, El blog de Stripe usa un layout editorial cronológico con estructuras de tarjetas coherentes que destacan la fecha y la categoría antes del titular.
- Vercel, El changelog y el blog de Vercel listan los artículos con etiquetas de categoría y fechas bien visibles, lo que coincide con la anatomía de tarjeta de una sección timeline.
- Linear, La página de changelog de Linear es una timeline vertical con bloques de contenido alternados, cada uno anclado a una fecha y conectado por una línea central.
- Framer, El feed de updates de Framer organiza las notas de versión en un layout de timeline cronológico con tarjetas y marcadores de fecha sobre un eje vertical.
FAQ
¿Cómo hago que la timeline sea responsive en mobile?
Cambia de un grid de tres columnas a un layout de una sola columna por debajo de tu breakpoint. Coloca cada tarjeta en la columna 1, oculta el espaciador y mueve el punto a una fila por encima de la tarjeta o conviértelo en un acento de borde izquierdo. La línea vertical central puede sustituirse por un borde izquierdo de 2px en el wrapper de la tarjeta.
¿Puedo reemplazar whileInView por un IntersectionObserver directo?
Sí, pero whileInView es más sencillo aquí porque gestiona internamente la configuración del observer, la limpieza y el flag once. Si necesitas evitar Framer Motion por completo, crea una instancia de observer, asígnala a todos los refs de tarjeta en un bucle y alterna una clase CSS que impulse la transición.
¿Por qué el punto central tiene un color diferente para el primer item?
El primer punto en color de acento marca la entrada más reciente como ancla visual de la timeline. Los puntos siguientes usan el color de borde para no competir con el contenido de las tarjetas por la atención. Puedes invertir esta lógica para timelines en orden ascendente donde el último item es el destacado.
¿La animación escalonada provoca layout shift?
La opacidad y la transform x son ambas propiedades gestionadas exclusivamente por el compositor, así que se ejecutan en la GPU sin tocar el layout. No hay ningún impacto en el CLS. Reserva el espacio de cada tarjeta en el flujo normal desde el principio para que las dimensiones del grid sean estables antes de que se dispare cualquier animación.