Retour au catalogue

About Magazine

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

aboutmedium Both Responsive a11y
editorialelegantagencyportfoliouniversalasymmetric
Theme

Créer une section about style magazine éditorial en React

Une section about style magazine éditorial en React combine une grille CSS 12 colonnes avec des animations whileInView décalées via Framer Motion. Le layout associe une grande image hero à un pull quote, puis enchaîne sur du texte multi-colonnes et un bloc de stats à côté d'une seconde image portrait.

  • Stack : React + Framer Motion + Tailwind v4, ~70 lignes, zéro dépendance supplémentaire.
  • Animation : fade+slide déclenché au viewport (whileInView, once:true) avec des délais échelonnés de 0,1 à 0,5 s.
  • Layout : grille 12 colonnes sur grand écran, empilement mono-colonne sur mobile, entièrement responsive.
  • Accessible : blockquote sémantique, hiérarchie des titres préservée, couleurs via CSS custom properties uniquement.
  • Les images sont optionnelles : les emplacements utilisent var(--color-background-alt) si aucune URL n'est fournie.

Ce composant transpose la densité visuelle des magazines print éditoriaux dans une section about React. Une grille 12 colonnes casse la symétrie volontairement : une grande image hero ancre un côté, un pull quote flotte en face, le corps de texte se divise en trois colonnes, et une colonne de stats ferme la marche à côté d'une seconde image portrait. Chaque bloc apparaît en fondu au scroll avec un délai décalé, la section se construisant d'elle-même à mesure que l'utilisateur descend.

Anatomie

Le composant compte quatre bandes verticales. D'abord, un bloc en-tête (max-w-3xl) contient le badge et le titre principal, aligné à gauche sans centrage, comme une dateline de magazine. Ensuite, une grille 12 colonnes se divise en 7/5 : la cellule gauche affiche la première image en ratio 4:3 ; la cellule droite, centrée verticalement, contient un pull quote en italique serif avec bordure gauche. Puis une grille responsive de 1 à 3 colonnes distribue chaque paragraphe dans sa propre colonne de texte. Enfin, un nouveau split 12 colonnes (4/8) associe une liste verticale de stats à gauche et une image portrait plus haute à droite.

Comment ça marche

Chaque grand bloc est enveloppé dans une motion.div avec initial={{ opacity: 0, y: 30 }} et whileInView={{ opacity: 1, y: 0 }}. L'option viewport once:true déclenche chaque animation une seule fois et libère l'écouteur. Les délais augmentent de 0,1 s par bloc (0,1, 0,2, 0,3, 0,4, 0,5), créant un flux de lecture naturel sans calcul de position de scroll. Les colonnes de la grille sont définies avec les utilitaires Tailwind lg:col-span-* sur un parent 12 colonnes, donc l'asymétrie est du CSS pur, sans logique de layout JavaScript.

Comment le coder en React

  1. Mettre en place le conteneur grille 12 colonnes

    Entoure la bande image+citation d'un div avec `grid grid-cols-1 lg:grid-cols-12 gap-8`. Assigne `lg:col-span-7` à la cellule image et `lg:col-span-5` à la cellule citation. Le conteneur parent max-w-6xl gère le centrage horizontal.

    <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. Ajouter le pull quote avec un blockquote sémantique

    Utilise un `<blockquote>` natif avec une bordure gauche pilotée par `var(--color-accent)` et du texte en italique serif. Un `<p>` séparé dessous porte l'attribution de l'auteur. Les deux sont rendus conditionnellement, si aucune prop pullQuote n'est passée, aucun n'apparaît.

    <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. Câbler les animations whileInView décalées

    Remplace chaque `div` par une `motion.div` et passe `initial={{ opacity: 0, y: 30 }}`, `whileInView={{ opacity: 1, y: 0 }}`, `viewport={{ once: true }}` et un `transition={{ duration: 0.6, delay: N }}`. Incrémente le délai de 0,1 s pour chaque bloc successif afin qu'ils cascadent vers le bas.

    <motion.div
      initial={{ opacity: 0, y: 30 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true }}
      transition={{ duration: 0.6, delay: 0.3 }}
    >
  4. Afficher la colonne de stats

    Mappe le tableau `stats` dans une pile `space-y-8`. Chaque élément affiche la `value` dans un grand titre coloré avec l'accent et le `label` en petit texte atténué dessous. Associe cette colonne (col-span-4) à la seconde image (col-span-8) avec le même pattern de grille 12 colonnes de l'étape 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>
    ))}

Quand l'utiliser

À utiliser quand l'histoire de marque a besoin de poids visuel : agences, studios, SaaS premium, blogs éditoriaux. La grille asymétrique et le pull quote communiquent un soin et une intentionnalité qu'un layout deux colonnes centré ne peut pas transmettre. À éviter sur les pages marketing légères où le temps d'écran est court, les révélations décalées nécessitent que l'utilisateur parcoure toute la section, elles récompensent les lecteurs plutôt que les scanners. Sur les pages avec des objectifs de conversion stricts, un bloc about plus compact sans délai d'animation sera plus performant.

Utilisé par

  • Stripe, Page about multi-blocs mélangeant grande photographie, déclarations style pull quote et stats numériques dans une grille éditoriale.
  • Loom, Grille image-texte asymétrique avec des métriques en gras utilisée pour raconter la croissance de l'entreprise.
  • Notion, Sections style éditorial associant photographie et déclarations de mission avec des chiffres clés, révélées progressivement au scroll.

FAQ

Peut-on utiliser plus de deux images ?

Le composant accepte image1 et image2 comme props séparées. Pour ajouter d'autres images, étends l'interface des props et ajoute des blocs image motion.div supplémentaires dans la grille, le système 12 colonnes offre assez de flexibilité pour les placer en col-span-4, 6 ou 8 selon les besoins.

Comment désactiver les animations au scroll pour un rendu statique ?

Remplace chaque motion.div par un div ordinaire. Le layout et les styles sont indépendants de Framer Motion, supprimer les wrappers d'animation ne change rien visuellement au repos.

Le tableau paragraphs n'a que deux éléments, pourquoi utiliser une grille 3 colonnes ?

La grille est toujours 3 colonnes sur grand écran quel que soit le nombre de paragraphes dans le tableau. Avec deux éléments, la troisième colonne reste vide, c'est un espace blanc intentionnel. Passe trois paragraphes pour la remplir, ou change lg:grid-cols-3 en lg:grid-cols-2 si tu fournis toujours deux paragraphes.

L'option viewport once:true cause-t-elle des problèmes avec les transitions de page ?

Avec once:true, chaque bloc s'anime à la première entrée et ne rejoue jamais. Sur les chargements de page standard, c'est le comportement correct. Si ton application utilise la navigation côté client et remonte le composant à la navigation en arrière, Framer Motion redéclenchera l'animation parce que le composant est réellement remonté, donc once:true reste 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

Avis

Section about style magazine en React, Layout + Code