Retour au catalogue

Careers Hover Reveal

Liste de postes minimaliste. Au hover, un panneau se revele avec description, requirements et bouton postuler. Slide depuis la droite avec AnimatePresence.

careerscomplex Both Responsive a11y
minimalelegantsaasuniversalagencysplit
Theme

Créer une liste de postes React avec panneau détail au survol

Une liste de postes avec reveal au hover suit la ligne active avec un simple index useState, puis utilise AnimatePresence de Framer Motion pour faire glisser un panneau détail depuis la droite à chaque changement d'index actif. Le panneau précédent sort avant que le suivant entre grâce au mode="wait", évitant tout chevauchement.

  • Stack : React 18 + Framer Motion 11 + Lucide React, ~160 lignes, zéro dépendance supplémentaire.
  • API clé : useState pour l'index actif, AnimatePresence mode="wait", motion.div avec transitions x/opacity.
  • Le panneau détail est sticky (top: 6rem) pour rester visible pendant que l'utilisateur parcourt une longue liste.
  • Accessible : les titres de postes utilisent des balises h3 ; le bouton postuler est un lien natif avec une vraie URL.
  • Sur mobile les deux colonnes se replient, le panneau détail se place sous la liste, toujours utilisable au toucher via onClick.

Careers Hover Reveal est une section React en split-layout conçue pour les pages recrutement. Une liste lisible des postes ouverts s'affiche à gauche ; survoler une ligne fait glisser depuis la droite un panneau détail avec la description complète, les prérequis et le bouton postuler. Le tout crée un flux de lecture façon magazine qui permet aux candidats d'explorer les offres sans quitter la page.

Anatomie

La mise en page est une rangée flex avec deux enfants flex qui se replient sur les petits écrans. La colonne gauche est une liste empilée de lignes de postes, chacune affichant titre, badge département, lieu et type de contrat. Une icône flèche anime son opacité et son décalage x pour signaler la ligne active. La colonne droite contient un conteneur sticky avec un unique emplacement AnimatePresence : quand active est non-nul, une carte détail s'affiche avec le label département, le titre, la description, une liste de prérequis animée et un lien postuler.

Comment ça marche

Toute l'interaction repose sur un seul morceau de state : `const [active, setActive] = useState<number | null>(null)`. Chaque ligne de poste met active à jour au mouseEnter et le bascule au clic (pour les utilisateurs tactiles). Le panneau détail est enveloppé dans AnimatePresence avec mode="wait", qui laisse l'élément sortant se démonter avant de monter le suivant. Le panneau entre avec `{ opacity: 0, x: 30 }` et sort avec les mêmes valeurs, la transition ressemble à un échange latéral plutôt qu'à un simple fondu. À l'intérieur du panneau, chaque `<li>` de prérequis est décalé via un délai par item (`delay: ri * 0.04`) pour que la liste cascade après que la carte s'est posée.

Comment le coder en React

  1. Mettre en place le split layout et l'état

    Crée un conteneur flex avec deux enfants : une colonne liste (flex: 1 1 340px) et une colonne détail (flex: 1 1 380px). Déclare `const [active, setActive] = useState<number | null>(null)` à la racine du composant. Cet unique entier pilote toute la partie interactive.

    const [active, setActive] = useState<number | null>(null);
  2. Construire les lignes de postes avec handlers hover et clic

    Itère sur ton tableau de postes et rends une motion.div pour chaque entrée. Attache `onMouseEnter={() => setActive(i)}` pour le desktop et `onClick={() => setActive(active === i ? null : i)}` pour basculer le panneau sur mobile. Anime l'arrière-plan de transparent vers ta couleur alt-background quand la ligne est active.

    onMouseEnter={() => setActive(i)}
    onClick={() => setActive(active === i ? null : i)}
  3. Envelopper le panneau détail dans AnimatePresence

    Place AnimatePresence avec mode="wait" dans la colonne droite. À l'intérieur, rends conditionnellement une motion.div avec la clé `active`. Définis initial/exit sur `{ opacity: 0, x: 30 }` et animate sur `{ opacity: 1, x: 0 }`. Le changement de clé force AnimatePresence à sortir l'ancien panneau et entrer le nouveau à chaque changement de poste actif.

    <AnimatePresence mode="wait">
      {active !== null && (
        <motion.div
          key={active}
          initial={{ opacity: 0, x: 30 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: 30 }}
          transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
        >
          {/* job detail content */}
        </motion.div>
      )}
    </AnimatePresence>
  4. Décaler en cascade la liste des prérequis dans le panneau

    Mappe les prérequis en éléments `<motion.li>`. Donne à chacun `initial={{ opacity: 0, x: -8 }}`, `animate={{ opacity: 1, x: 0 }}`, et un délai de transition de `ri * 0.04` secondes. Comme AnimatePresence remont le panneau à chaque changement d'actif, la cascade se rejoue automatiquement pour chaque nouveau poste.

    <motion.li
      key={ri}
      initial={{ opacity: 0, x: -8 }}
      animate={{ opacity: 1, x: 0 }}
      transition={{ duration: 0.25, delay: ri * 0.04 }}
    >

Quand l'utiliser

Ce pattern convient quand une entreprise a 4 à 12 postes ouverts et veut une section carrières dédiée plutôt qu'une simple liste. Il fonctionne bien en fin de page aux côtés d'un bloc culture ou avantages. À éviter si tu dépasses 20 postes, un tableau filtrable ou une interface de recherche passe mieux à l'échelle. Sur les pages à contenu critique où chaque pixel compte, la colonne du panneau sticky occupe la moitié de la largeur d'écran sur desktop.

Utilisé par

  • Notion, Utilise une page carrières en split-layout épurée où cliquer sur un poste révèle les détails en ligne sans navigation de page.
  • Linear, Liste de postes avec un design de ligne minimal et une vue détail contextuelle qui garde le candidat concentré sur la liste.
  • Vercel, Postes groupés par département avec comportement d'expansion inline, maintenant l'expérience de navigation sur une seule page.
  • Stripe, Navigateur de postes en panneau divisé avec filtres par département et une colonne détail persistante pour le poste sélectionné.

FAQ

Pourquoi utiliser AnimatePresence mode="wait" plutôt que mode="sync" ?

mode="wait" garantit que le panneau sortant se démonte complètement avant que le nouveau entre, évitant que deux cartes détail soient visibles simultanément. Avec mode="sync" les deux panneaux se chevauchent brièvement pendant la transition, ce qui paraît chaotique quand l'utilisateur survole rapidement plusieurs lignes.

Comment ajouter le panneau sur mobile sans hover ?

Le handler onClick gère déjà le toucher : taper une ligne la rend active et un second tap la désélectionne. Sur petits écrans les deux colonnes flex se replient et le panneau détail se place sous la liste. Tu peux faire défiler le panneau en vue avec une ref et scrollIntoView au changement d'actif.

Combien de postes ce composant peut-il gérer avant de devenir difficile à utiliser ?

Le composant fonctionne mieux avec 4 à 12 postes. Au-delà, la colonne gauche devient un long défilement et le panneau sticky commence à paraître déconnecté. Pour des listes plus longues, passe à un tableau filtrable avec un tiroir ou une modale pour la vue détail.

Puis-je déclencher le panneau depuis un hash d'URL pour faire un lien direct vers un poste ?

Oui. Au montage, lis window.location.hash, trouve l'index du poste correspondant et appelle setActive avec lui. Combine cela avec un effet qui met à jour le hash quand active change. Le composant utilise déjà des indices numériques, il suffit de mapper chaque poste sur un slug stable pour le hash.

"use client";

import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
import { ArrowRight, MapPin, Clock } from "lucide-react";

interface Job {
  title: string;
  department: string;
  location: string;
  type: string;
  description: string;
  requirements: string[];
  url: string;
}

interface CareersHoverRevealProps {
  title?: string;
  subtitle?: string;
  jobs?: Job[];
}

Code complet réservé à Pro

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

Passer en Pro, 9,99€/mois

Avis

Liste de postes React avec panneau reveal au hover, Tutoriel