Retour au catalogue

About Magazine

Layout editorial style magazine avec citations, grandes images et texte multi-colonnes.

aboutmedium Both Responsive a11y
editorialelegantagencyportfoliouniversalasymmetric
Theme

How to build a magazine editorial about section in React

A magazine editorial about section in React combines a 12-column CSS grid with staggered whileInView animations from Framer Motion. The layout pairs a large hero image with a pull quote, then flows into multi-column body text and a stat block beside a second portrait image.

  • Stack: React + Framer Motion + Tailwind v4, ~70 lines, zero extra dependencies.
  • Animation: viewport-triggered fade+slide (whileInView, once:true) with staggered delays from 0.1 to 0.5 s.
  • Layout: 12-column grid on large screens, single-column stack on mobile, fully responsive.
  • Accessible: semantic blockquote, heading hierarchy preserved, color via CSS custom properties only.
  • Images are optional: placeholders use var(--color-background-alt) if no URL is passed.

This component brings the visual density of editorial print magazines to a React about section. A 12-column grid breaks symmetry on purpose: a wide hero image anchors one side, a pull quote floats opposite, body text splits into three columns, and a column of stats bookends a second portrait image. Every block fades up on scroll with a staggered delay, so the section builds itself as the user reads down.

Anatomy

The component has four vertical bands. First, a header block (max-w-3xl) holds the badge and main heading, left-aligned, uncentered, like a magazine dateline. Second, a 12-column grid splits 7/5: the left cell shows the first image in a 4:3 aspect ratio; the right cell, vertically centered, holds a bordered pull quote in serif italic. Third, a 1-to-3 responsive column grid maps each paragraph in the array to its own text column. Fourth, another 12-column split (4/8) pairs a vertical stats list on the left with a taller portrait image on the right.

How it works

Every major block is wrapped in a motion.div with initial={{ opacity: 0, y: 30 }} and whileInView={{ opacity: 1, y: 0 }}. The viewport option once:true fires each animation once and clears the listener. Delays increase by 0.1 s per block (0.1, 0.2, 0.3, 0.4, 0.5), creating a natural reading flow without any scroll-position math. The grid columns are defined with Tailwind's lg:col-span-* utilities against a 12-column parent, so the asymmetry is pure CSS, no JavaScript layout logic.

How to build it in React

  1. Set up the 12-column grid container

    Wrap the image+quote band in a div with `grid grid-cols-1 lg:grid-cols-12 gap-8`. Assign `lg:col-span-7` to the image cell and `lg:col-span-5` to the quote cell. The parent max-w-6xl container handles horizontal centering.

    <div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
      <div className="lg:col-span-7">/* image */</div>
      <div className="lg:col-span-5 flex flex-col justify-center">/* quote */</div>
    </div>
  2. Add the pull quote with a semantic blockquote

    Use a native `<blockquote>` with a left border driven by `var(--color-accent)` and serif italic text. A separate `<p>` below carries the author attribution. Both are conditionally rendered, if no pullQuote prop is passed, neither appears.

    <blockquote
      className="text-xl md:text-2xl font-serif italic leading-relaxed border-l-2 pl-6"
      style={{ color: "var(--color-foreground)", borderColor: "var(--color-accent)" }}
    >
      {pullQuote}
    </blockquote>
  3. Wire up staggered whileInView animations

    Swap each `div` for a `motion.div` and pass `initial={{ opacity: 0, y: 30 }}`, `whileInView={{ opacity: 1, y: 0 }}`, `viewport={{ once: true }}`, and a `transition={{ duration: 0.6, delay: N }}`. Increment the delay by 0.1 s for each successive block so they cascade downward.

    <motion.div
      initial={{ opacity: 0, y: 30 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true }}
      transition={{ duration: 0.6, delay: 0.3 }}
    >
  4. Render the stats column

    Map the `stats` array into a `space-y-8` stack. Each item renders the `value` in a large accent-colored heading and the `label` in muted small text below. Pair this column (col-span-4) with the second image (col-span-8) using the same 12-column grid pattern from step 1.

    {stats.map((stat, i) => (
      <div key={i}>
        <p className="text-3xl font-bold" style={{ color: "var(--color-accent)" }}>{stat.value}</p>
        <p className="text-sm mt-1" style={{ color: "var(--color-foreground-muted)" }}>{stat.label}</p>
      </div>
    ))}

When to use it

Reach for it when the brand story needs visual weight: agencies, studios, premium SaaS, editorial blogs. The asymmetric grid and pull quote communicate craft and intentionality that a centered two-column layout cannot. Skip it on lean marketing pages where screen time is short, the staggered reveals require the user to scroll through the whole section, so it rewards readers rather than scanners. On pages with strict conversion goals, a tighter about block without animation delay will perform better.

Used by

  • Stripe, Multi-block about page mixing large photography, pull-quote-style statements and numerical stats in an editorial grid.
  • Loom, Asymmetric image-text grid with bold metric highlights used to tell the company growth story.
  • Notion, Editorial-style sections pairing photography with mission statements and key numbers, scroll-revealed progressively.

FAQ

Can I use more than two images?

The component accepts image1 and image2 as separate props. To add more images, extend the props interface and drop additional motion.div image blocks into the grid, the 12-column system gives you enough flexibility to place them at col-span-4, 6, or 8 as needed.

How do I disable the scroll animations for a static render?

Replace each motion.div with a plain div. The layout and styles are independent of Framer Motion, removing the animation wrappers changes nothing visually at rest.

The paragraphs array shows only two items, why does it use a 3-column grid?

The grid is always 3 columns on large screens regardless of how many paragraphs are in the array. With two items, the third column stays empty, this is intentional white space. Pass three paragraphs to fill it, or change lg:grid-cols-3 to lg:grid-cols-2 if you always supply two.

Does the once:true viewport option cause problems with page transitions?

With once:true, each block animates on first entry and never replays. On standard page loads this is the correct behavior. If your app uses client-side navigation and remounts the component on back-navigation, Framer Motion will re-trigger the animation because the component is truly remounted, so once:true stays correct.

"use client";

import React from "react";
import { motion } from "framer-motion";

interface AboutMagazineProps {
  badge?: string;
  title?: string;
  pullQuote?: string;
  quoteAuthor?: string;
  image1?: string;
  image2?: string;
  paragraphs?: string[];
  stats?: { label: string; value: string }[];
}

export default function AboutMagazine({ badge, title, pullQuote, quoteAuthor, image1, image2, paragraphs = [], stats = [] }: AboutMagazineProps) {
  return (
    <section className="py-[var(--section-padding-y,6rem)]" style={{ backgroundColor: "var(--color-background)" }}>
      <div className="mx-auto max-w-6xl px-[var(--container-padding-x,1.5rem)]">
        {/* Header */}
        <motion.div initial={{ opacity: 0, y: 20 }} whileInView={{ opacity: 1, y: 0 }} viewport={{ once: true }} transition={{ duration: 0.6 }} className="max-w-3xl mb-16">

Code complet réservé à Pro

Code source intégral, export multi-framework et playground.

Passer en Pro, 9,99€/mois

Reviews

React Magazine-Style About Section, Layout + Code