Retour au blog

Comment construire une section hero React avec Tailwind CSS

Publié le 16 mars 2026·6 min read

La section hero est l'espace le plus stratégique de votre landing page. Les visiteurs se forgent une impression en 50 millisecondes, donc votre hero doit communiquer sa valeur immédiatement. Ce guide vous accompagne pas à pas dans la construction d'une section hero production-ready en React avec Tailwind CSS v4 et Framer Motion — la même approche utilisée sur des dizaines de landing pages SaaS à fort taux de conversion.

Ce qui fait une bonne section hero

Avant d'écrire la moindre ligne de JSX, clarifiez ce que le composant doit accomplir :

  • Titre principal — une proposition de valeur claire, 6 à 10 mots maximum
  • Sous-titre — 1 à 2 phrases qui développent le titre et adressent le problème du lecteur
  • CTA principal — un seul bouton, avec un libellé orienté action ("Commencer à construire", pas "Valider")
  • CTA secondaire — optionnel, engagement plus faible ("Voir les exemples", "Regarder la démo")
  • Preuve sociale — avatars, note en étoiles, ou un chiffre unique ("Utilisé par 4 200+ équipes")
  • Badge — pill d'annonce optionnel au-dessus du titre ("Nouveau : support du mode sombre")

Structure du composant

Commencez par l'interface TypeScript. Des props typées évitent les erreurs au runtime et rendent le composant auto-documenté :

// HeroCentered.tsx
"use client";

import { motion } from "framer-motion";
import { ArrowRight } from "lucide-react";

interface HeroCenteredProps {
  badge?: string;
  title: string;
  titleAccent?: string;
  description: string;
  ctaLabel: string;
  ctaUrl: string;
  ctaSecondaryLabel?: string;
  ctaSecondaryUrl?: string;
  socialProof?: {
    avatars: string[];   // image URLs
    count: number;
    label: string;
  };
}

La directive "use client" est nécessaire car Framer Motion utilise des API navigateur. Dans Next.js 15, tous les composants sont des Server Components par défaut, donc on opte explicitement pour le rendu côté client.

Layout et thématisation

Plutôt que de coder les couleurs en dur comme bg-white ou text-gray-900, utilisez des propriétés CSS personnalisées. Le composant devient ainsi conscient du thème — changez un attribut data-theme sur un élément parent et toute la section se repeint instantanément :

export default function HeroCentered({ title, titleAccent, description, ctaLabel, ctaUrl, badge }: HeroCenteredProps) {
  return (
    <section
      style={{
        position: "relative",
        overflow: "hidden",
        paddingTop: "var(--section-padding-y-lg)",
        paddingBottom: "var(--section-padding-y-lg)",
        background: "var(--color-background)",
        minHeight: "85vh",
        display: "flex",
        alignItems: "center",
      }}
    >
      <div
        style={{
          width: "100%",
          maxWidth: "var(--container-max-width)",
          margin: "0 auto",
          padding: "0 var(--container-padding-x)",
        }}
      >
        <div style={{ maxWidth: "720px", margin: "0 auto", textAlign: "center" }}>
          {/* Content goes here */}
        </div>
      </div>
    </section>
  );
}

Le token CSS --section-padding-y-lg vaut 8rem par défaut. --container-max-width fait 1280px. Ces deux valeurs sont définies globalement et héritées automatiquement.

Animations d'entrée Framer Motion avec stagger

Un stagger à l'entrée donne au hero un aspect soigné sans être distrayant. Chaque élément entre 60 à 80 ms après le précédent :

const EASE = [0.16, 1, 0.3, 1] as const; // custom spring-like cubic-bezier

// Badge
<motion.div
  initial={{ opacity: 0, y: 8 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.4, ease: EASE }}
>
  <span style={{
    padding: "0.5rem 1.25rem",
    borderRadius: "var(--radius-full)",
    border: "1px solid var(--color-accent-border)",
    background: "var(--color-accent-subtle)",
    color: "var(--color-foreground-muted)",
    fontSize: "0.8125rem",
    fontWeight: 500,
  }}>
    {badge}
  </span>
</motion.div>

// Headline — starts 60ms after badge
<motion.h1
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.6, delay: 0.06, ease: EASE }}
  style={{
    fontSize: "clamp(2.5rem, 5vw, 4.5rem)",
    fontWeight: 700,
    lineHeight: 1.08,
    letterSpacing: "-0.03em",
    color: "var(--color-foreground)",
  }}
>
  {title}{" "}
  <em style={{
    fontStyle: "italic",
    fontWeight: 400,
    textDecoration: "underline",
    textDecorationColor: "var(--color-accent)",
    textDecorationThickness: "3px",
    textUnderlineOffset: "6px",
  }}>
    {titleAccent}
  </em>
</motion.h1>

// Description — starts 140ms after badge
<motion.p
  initial={{ opacity: 0, y: 12 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 0.5, delay: 0.14, ease: EASE }}
  style={{
    fontSize: "1.125rem",
    lineHeight: 1.7,
    color: "var(--color-foreground-muted)",
  }}
>
  {description}
</motion.p>

Le clamp() sur le font-size gère le responsive sans media queries — la taille croît linéairement de 2.5rem sur mobile jusqu'à 4.5rem sur grand écran.

Traitements de fond

Trois techniques fonctionnent particulièrement bien pour les arrière-plans de hero :

1. Halo de couleur accent

Un cercle flou et semi-transparent dans la couleur d'accent crée de la profondeur sans distraire du contenu :

<div
  aria-hidden
  style={{
    position: "absolute",
    top: "-20%",
    left: "50%",
    transform: "translateX(-50%)",
    width: "60vw",
    height: "60vw",
    maxWidth: "800px",
    borderRadius: "50%",
    background: "var(--color-accent)",
    opacity: 0.06,
    filter: "blur(100px)",
    pointerEvents: "none",
  }}
/>

L'attribut aria-hidden masque l'élément décoratif aux lecteurs d'écran. Une opacité à 0.06 est suffisamment subtile pour fonctionner sur n'importe quel thème.

2. Grille de points

Une grille SVG de points répétée ajoute de la texture :

<div
  aria-hidden
  style={{
    position: "absolute",
    inset: 0,
    backgroundImage: "radial-gradient(circle, var(--color-border) 1px, transparent 1px)",
    backgroundSize: "24px 24px",
    opacity: 0.5,
    maskImage: "radial-gradient(ellipse 70% 60% at 50% 50%, black, transparent)",
  }}
/>

Le maskImage estompe la grille sur les bords pour qu'elle ne rentre pas en conflit avec la section suivante.

3. Bruit subtil

Une texture de bruit en data URI SVG 200×200px ajoute une touche premium :

background-image: url("data:image/svg+xml,...");
opacity: 0.03;

Bouton CTA

Le bouton principal utilise la couleur d'accent avec une transition de survol fluide :

<a
  href={ctaUrl}
  style={{
    display: "inline-flex",
    alignItems: "center",
    gap: "8px",
    padding: "0.875rem 2rem",
    borderRadius: "var(--radius-full)",
    background: "var(--color-accent)",
    color: "var(--color-foreground)",
    fontWeight: 600,
    fontSize: "0.9375rem",
    textDecoration: "none",
    transition: "background var(--duration-normal) var(--ease-out)",
  }}
  onMouseEnter={(e) => {
    (e.target as HTMLAnchorElement).style.background = "var(--color-accent-hover)";
  }}
  onMouseLeave={(e) => {
    (e.target as HTMLAnchorElement).style.background = "var(--color-accent)";
  }}
>
  {ctaLabel}
  <ArrowRight style={{ width: 16, height: 16 }} />
</a>

Checklist accessibilité

Avant de mettre en production, vérifiez :

  • <section> a un nom accessible via aria-label ou un <h1> visible à l'intérieur
  • Les liens CTA ont un texte descriptif (pas "Cliquez ici")
  • Les éléments décoratifs sont aria-hidden
  • Le ratio de contraste respecte WCAG AA (4,5:1 pour le texte normal, 3:1 pour le grand texte)
  • Le composant respecte prefers-reduced-motion :
import { useReducedMotion } from "framer-motion";

const shouldReduceMotion = useReducedMotion();

// Use duration: 0 when shouldReduceMotion is true
transition={{ duration: shouldReduceMotion ? 0 : 0.6, ease: EASE }}

Variantes hero prêtes à l'emploi

Construire un hero de zéro prend du temps, et l'optimisation du taux de conversion est un travail continu. Plutôt que de partir de rien, parcourez le catalogue hero d'Incubator — il comprend des heroes centrés, des heroes en split-layout, des backgrounds vidéo plein écran, des heroes avec texte animé, et bien d'autres, tous construits avec le même système de tokens CSS et les patterns Framer Motion décrits ci-dessus. Chaque variante est livrée avec des props TypeScript et des données mock, prête à intégrer dans votre application Next.js.

VA

Victor Aubague

Développeur & créateur d'Incubator

Développeur full-stack spécialisé en React, Next.js et TypeScript. J'ai créé Incubator pour aider les développeurs à livrer de belles interfaces plus rapidement — tous les composants sont issus de vrais projets clients et de code en production.

LinkedIn
Comment construire une section hero React avec Tailwind CSS — Incubator