Créer un formulaire d'inscription React avec animation d'entrée
Un formulaire d'inscription React centre généralement une carte avec logo de marque, bouton OAuth social optionnel, une rangée en grille prénom/nom, email, mot de passe, confirmation et une case CGU. Enveloppe la carte dans un div Framer Motion avec `initial={{ opacity: 0, y: 20 }}` et `animate={{ opacity: 1, y: 0 }}` pour une entrée soignée sans dépendance supplémentaire.
- Stack : React 18 + Framer Motion 11 + Tailwind v4, ~70 lignes, zéro dépendance supplémentaire.
- Animation d'entrée : une seule motion.div, cubic-bezier [0.16, 1, 0.3, 1], durée 0.6s.
- La rangée de login social est conditionnelle via la prop `showSocial`, désactivable pour les flux email uniquement.
- Toutes les couleurs viennent de custom properties CSS (--color-accent, --color-background, etc.), thème interchangeable sur les 7 presets.
- Accessible : <label> + checkbox natifs, élément form sémantique, champs password typés pour l'autocomplétion navigateur.
AuthRegister est une carte d'inscription minimaliste et centrée pensée pour les SaaS et les produits universels. Elle couvre toute la surface d'inscription, logo de marque, raccourci OAuth social, rangée prénom/nom séparée, email, mot de passe, confirmation et acceptation des CGU, dans un seul composant composable. La carte entière glisse à l'affichage via une seule animation Framer Motion, gardant l'interaction légère sans configuration complexe.
Anatomie
Le composant est structuré en trois zones verticales dans une colonne `max-w-sm` centrée. En haut : un carré monogramme de marque (première lettre de `brandName`), un H1 et un sous-titre optionnel. Au centre : un bouton Google OAuth conditionnel suivi d'un séparateur, puis le formulaire, deux inputs côte à côte dans une grille 2 colonnes (prénom / nom), suivis des inputs email, mot de passe et confirmation empilés, une case CGU et un bouton de soumission pleine largeur. En bas : un lien de redirection vers la connexion. La section extérieure couvre `min-h-screen` pour que la carte reste centrée verticalement quelle que soit la hauteur d'écran.
Comment ça marche
L'animation est volontairement simple : une `motion.div` enveloppe toute la carte avec `initial={{ opacity: 0, y: 20 }}` et `animate={{ opacity: 1, y: 0 }}`. Le cubic-bezier personnalisé `[0.16, 1, 0.3, 1]` est une courbe expo-out, elle accélère rapidement et décélère doucement, faisant paraître la carte posée en place plutôt que simplement fondue. La durée est 0.6s, assez longue pour paraître intentionnelle mais assez courte pour ne pas bloquer l'utilisateur. Pas de scroll trigger ni de stagger, juste une entrée propre au chargement de page qui fonctionne partout sans configuration supplémentaire.
Comment le coder en React
Mettre en place la mise en page centrée
Enveloppe tout dans une `section` avec `min-h-screen flex items-center justify-center`. Définis le background sur `var(--color-background)` via un style inline pour respecter le preset de thème actif. À l'intérieur, place une `motion.div` limitée à `max-w-sm w-full`, c'est le périmètre de la carte.
<section className="min-h-screen flex items-center justify-center py-16 px-6" style={{ background: "var(--color-background)" }} > <motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }} className="w-full max-w-sm" >Ajouter l'en-tête de marque et le bouton social optionnel
Rends un petit carré avec le premier caractère de `brandName` comme monogramme, stylé avec le fond `--color-accent`. En dessous, place le titre H1 et le sous-titre optionnel. Ensuite, rends conditionnellement le bouton Google et un séparateur selon la prop `showSocial`. Le séparateur utilise une rangée flex avec deux lignes `h-px` et un label centré 'ou'.
{showSocial && ( <> <button style={{ border: "1px solid var(--color-border)", color: "var(--color-foreground)" }} className="w-full py-2.5 rounded-lg text-sm font-medium mb-3 transition-opacity hover:opacity-80"> Sign up with Google </button> <div className="flex items-center gap-3 my-6"> <div className="flex-1 h-px" style={{ background: "var(--color-border)" }} /> <span className="text-xs" style={{ color: "var(--color-foreground-light)" }}>or</span> <div className="flex-1 h-px" style={{ background: "var(--color-border)" }} /> </div> </> )}Construire les champs du formulaire
Dans un `form` avec `onSubmit={(e) => e.preventDefault()}`, ouvre par un div `grid grid-cols-2 gap-3` contenant les inputs prénom et nom. Empile email, mot de passe et confirmation en dessous. Tous les inputs partagent le même style : `background: var(--color-background-alt)`, `border: 1px solid var(--color-border)` et `color: var(--color-foreground)`. Cela les rend sensibles au thème sans coder de hex en dur.
<div className="grid grid-cols-2 gap-3"> <input type="text" placeholder="First name" style={{ background: "var(--color-background-alt)", color: "var(--color-foreground)", border: "1px solid var(--color-border)" }} className="w-full px-4 py-3 rounded-lg text-sm outline-none" /> <input type="text" placeholder="Last name" ... /> </div> <input type="email" placeholder="Email" ... /> <input type="password" placeholder="Password" ... /> <input type="password" placeholder="Confirm password" ... />Ajouter la case CGU et le bouton de soumission
Enveloppe un `<input type='checkbox'>` natif dans un `<label>` avec `flex items-start gap-2`. Cela rend la case à cocher et le texte des CGU accessibles sans composant custom. Le bouton de soumission prend toute la largeur avec `background: var(--color-accent)` et `color: var(--color-background)`. Ferme le formulaire par un paragraphe renvoyant vers la page de connexion via la prop `loginUrl`.
Quand l'utiliser
Ce composant convient à tout produit qui a besoin d'un écran d'inscription rapide et autonome : dashboards SaaS, outils internes, side projects ou portails admin. La prop `showSocial` facilite le basculement entre flux OAuth-first et email uniquement sans forker le composant. À éviter quand ton flux d'inscription nécessite un onboarding multi-étapes (barre de progression, sélection de rôle, choix de plan), ces flux demandent un wizard avec état, pas un formulaire en carte unique. Passe aussi ton chemin si tu as besoin d'une vraie validation de formulaire avec états d'erreur par champ ; ajoute une lib comme react-hook-form avant de shipper.
Utilisé par
- Linear, Carte d'inscription centrée avec bouton Google OAuth et champs minimalistes, correspondant exactement à ce pattern.
- Vercel, Flux d'inscription avec options sociales prioritaires (GitHub/Google) au-dessus d'un séparateur, puis fallback email en dessous, le même bloc social conditionnel qu'utilise ce composant.
- Supabase, Bibliothèque Auth UI construite sur le même pattern de carte centrée, champs de nom séparés et confirmation de mot de passe.
- Clerk, Composant d'inscription prêt à l'emploi avec emplacement logo de marque, rangée OAuth social et champs email/mot de passe empilés, implémentation de référence de ce pattern de carte dans l'écosystème React.
FAQ
Comment ajouter une vraie validation de formulaire à ce composant ?
Remplace les `<input>` nus par le `register` de react-hook-form et affiche les messages d'erreur par champ en dessous de chaque input. La mise en page reste identique ; tu ajoutes juste un hook `useForm` et des balises `<span>` d'erreur conditionnelles. Pour le format email et la robustesse du mot de passe, utilise l'option pattern intégrée ou un schéma Zod avec l'adaptateur zodResolver.
Peut-on passer à une mise en page deux colonnes sur les grands écrans ?
La carte est limitée à `max-w-sm` par choix. Pour une mise en page fractionnée, illustration à gauche, formulaire à droite, voir la variante auth-login-split, avec laquelle ce composant s'associe. Étirer un formulaire mono-colonne au-delà de ~380px nuit à l'utilisabilité ; l'espace supplémentaire est mieux utilisé pour un panneau visuel ou de preuve sociale.
Que se passe-t-il si l'utilisateur a activé reduced-motion ?
Framer Motion respecte la media query `prefers-reduced-motion` par défaut quand tu utilises le hook `useReducedMotion` et conditionnes tes transitions. Dans l'implémentation actuelle, tu dois ajouter `const shouldReduce = useReducedMotion()` et remplacer la transition par `duration: shouldReduce ? 0 : 0.6` pour désactiver l'animation pour les utilisateurs qui l'ont désactivée.
La case CGU est-elle connectée pour désactiver le bouton de soumission ?
Pas dans ce composant de base, la case est rendue en non-contrôlé. Pour forcer l'acceptation avant soumission, lève la case en état local avec `useState(false)` et ajoute `disabled={!accepted}` plus un style d'opacité réduite au bouton de soumission. Garde l'état désactivé visuellement distinct pour que les utilisateurs comprennent pourquoi le bouton est inactif.