Cómo crear una sección de blog estilo magazine en React
Una sección de blog magazine en React combina un amplio artículo destacado (dos columnas: imagen + texto) con un grid de tarjetas responsive auto-fill debajo. Framer Motion whileInView dispara reveals fade-up escalonados al hacer scroll, sin escribir ni un solo keyframe a mano.
- Stack: React + Framer Motion 11 + Lucide React, ~150 líneas, cero dependencias adicionales.
- Layout: CSS Grid, 1fr/1fr para el bloque destacado, repeat(auto-fill, minmax(280px, 1fr)) para el grid de tarjetas.
- Tematización: 100% CSS custom properties (--color-background, --color-accent, --radius-lg), cero colores codificados a mano.
- Accesible: jerarquía semántica h2/h3/h4, icono Calendar de Lucide solo decorativo, enlaces como elementos <a> nativos.
- Advertencia en móvil: el grid de 2 columnas del destacado solo se reduce a 1 columna si se añade un breakpoint o media query, no incluido en las 150 líneas base.
Blog Magazine es una sección React que da a tu contenido una jerarquía de tipo prensa: un artículo principal ocupa todo el ancho a la izquierda, su texto editorial a la derecha, y de tres a seis tarjetas secundarias rellenan un grid fluido debajo. Se lee con claridad de un vistazo, escala de tres a una docena de artículos sin tocar el código del layout, y se anima al hacer scroll mediante un puñado de llamadas whileInView de Framer Motion.
Anatomía
La sección se divide en tres zonas apiladas. Arriba, una cabecera de sección con título h2 y subtítulo aparece con fade-up en el primer scroll. Debajo, una tarjeta CSS Grid de dos columnas hace de artículo destacado: la celda izquierda es un placeholder coloreado en proporción 16/10, la celda derecha es una columna flex con badge de categoría, titular h3, extracto y una fila de fecha con el icono Calendar de Lucide. La zona inferior es un CSS Grid con repeat(auto-fill, minmax(280px, 1fr)) que mapea el array articles en tarjetas más pequeñas, cada una con una miniatura 16/9, categoría, título h4 y fecha. Un enlace "ver todos" centrado con un icono ArrowRight cierra la sección.
Cómo funciona
Cada bloque visual es un motion.a o motion.div con initial={{ opacity: 0, y: 16 }} y whileInView={{ opacity: 1, y: 0 }}. El viewport se configura con once:true para que la animación se dispare exactamente una vez. El grid de tarjetas escalona cada elemento multiplicando su índice por 0.06 segundos en el delay de la transición. Una constante EASE compartida ([0.16, 1, 0.3, 1], una curva cúbica fast-out/slow-in) da a los reveals un toque editorial ágil en lugar de un fundido lineal genérico. El componente es presentacional: acepta un objeto de artículo destacado y un array de artículos como props, recurriendo a valores por defecto inline cuando no se proporciona ninguno.
Cómo crearlo en React
Definir las interfaces de datos y las props
Crea una interfaz Article (title, excerpt, date, category, imageColor) y una interfaz BlogMagazineProps que envuelva sectionTitle, sectionSubtitle, featured y articles. Mantén imageColor como una cadena CSS para poder pasar cualquier custom property o valor hex desde el componente padre.
interface Article { title: string; excerpt: string; date: string; category: string; imageColor: string; } interface BlogMagazineProps { sectionTitle?: string; featured?: Article; articles?: Article[]; }Construir el artículo destacado con un CSS Grid de 2 columnas
Usa un motion.a que envuelve un div con gridTemplateColumns:'1fr 1fr'. La celda izquierda es un div con aspectRatio:'16/10' y un color de fondo semitransparente. La celda derecha es una columna flex con el badge de categoría, el titular h3, el párrafo de extracto y una fila Calendar + fecha.
<motion.a initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.6, delay: 0.08, ease: EASE }} style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '2rem' }} >Renderizar el grid de tarjetas con delays escalonados
Mapea el array articles y envuelve cada tarjeta en un motion.a. Usa i * 0.06 como delay de transición para que las tarjetas se revelen secuencialmente de izquierda a derecha. El grid usa repeat(auto-fill, minmax(280px, 1fr)) para refluir automáticamente de una a cuatro columnas.
{articles.map((article, i) => ( <motion.a key={i} initial={{ opacity: 0, y: 16 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.45, delay: i * 0.06, ease: EASE }} > ... </motion.a> ))}Usar CSS custom properties para todos los colores y espaciados
Nunca codifiques un valor hex a mano. Usa var(--color-background), var(--color-foreground), var(--color-accent), var(--color-border), var(--radius-lg) y var(--section-padding-y) en todas partes. Así cualquier preset de tema del padre puede cambiar el aspecto sin tocar el source del componente.
Cuándo usarlo
Usa este layout cuando tu sitio tiene un programa de contenido real: un blog, una sala de prensa, un changelog o un hub de recursos con al menos cuatro a seis artículos. Funciona mejor en medio de una landing page como señal de confianza, o como plantilla principal de un índice de blog. Evítalo en páginas donde el blog es algo secundario con uno o dos posts, o donde necesitas un feed masonry o de scroll infinito: este grid espera un número predecible de elementos. En móvil, añade una media query para pasar el grid destacado de dos columnas a una; el grid de tarjetas ya refluye por sí solo.
Usado por
- Stripe, Presenta un gran artículo hero sobre un grid de publicaciones recientes, una aplicación directa de esta jerarquía magazine.
- Vercel, Índice de blog con un post destacado fijado arriba y un grid estilo masonry de artículos secundarios debajo.
- Linear, Índice de blog estilo magazine: una amplia tarjeta destacada, luego un grid de dos o tres columnas de entradas recientes.
- Liveblocks, El post destacado ocupa todo el ancho arriba, y tarjetas de artículos más pequeñas rellenan un grid responsive debajo.
FAQ
¿Cómo hago que el bloque destacado se apile verticalmente en móvil?
El bloque destacado usa CSS Grid con gridTemplateColumns:'1fr 1fr' definido en estilo inline. Añade una media query (o una clase responsive de Tailwind si pasas a className) que sobrescriba esto a gridTemplateColumns:'1fr' por debajo de tu breakpoint móvil. El grid de tarjetas de abajo ya refluye gracias a auto-fill.
¿Puedo reemplazar los placeholders imageColor por imágenes reales?
Reemplaza el div coloreado por un elemento img o next/image con objectFit:'cover' y el mismo estilo aspect-ratio. Cambia imageColor en la interfaz Article por una cadena imageUrl, y listo.
¿Por qué cada tarjeta se anima por separado en lugar de todas a la vez?
La expresión delay: i * 0.06 escalona las tarjetas en 60ms por índice. Esto da al grid un efecto cascada que se percibe como intencional en lugar de un pop-in en bloque, haciendo que el layout se sienta más ligero y editorial.
¿Funciona el componente sin ningún artículo pasado en las props?
El artículo destacado recurre a un objeto por defecto inline, así que la sección siempre renderiza algo. El array articles tiene por defecto un array vacío, por lo que el grid de tarjetas simplemente no renderiza nada sin lanzar errores. Pasa al menos tres artículos para que el layout se vea intencional.