La page de contact est souvent la dernière étape avant qu'un lead devienne une conversation. Un formulaire encombré ou intimidant tue les conversions. Un formulaire propre et bien structuré avec des labels clairs, des défauts intelligents et un feedback visuel facilite la prise de contact pour les visiteurs.
Ce guide couvre cinq patterns de formulaires de contact React avec Tailwind CSS — du formulaire simple à trois champs au wizard multi-étapes avec indicateur de progression.
1. Le formulaire de contact simple
Trois champs, un bouton. Nom, email, message. C'est tout ce dont la plupart des entreprises ont besoin.
"use client";
import { useState } from "react";
interface FormData {
name: string;
email: string;
message: string;
}
function ContactForm() {
const [form, setForm] = useState<FormData>({
name: "",
email: "",
message: "",
});
function handleChange(
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) {
setForm({ ...form, [e.target.name]: e.target.value });
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const res = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
if (res.ok) {
setForm({ name: "", email: "", message: "" });
}
}
return (
<form onSubmit={handleSubmit} className="mx-auto max-w-lg space-y-6">
<div>
<label htmlFor="name" className="block text-sm font-medium mb-2">
Nom
</label>
<input
id="name"
name="name"
type="text"
required
value={form.name}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 px-4 py-2.5 text-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900 dark:border-neutral-700 dark:bg-neutral-900 dark:focus:border-white dark:focus:ring-white"
/>
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium mb-2">
Email
</label>
<input
id="email"
name="email"
type="email"
required
value={form.email}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 px-4 py-2.5 text-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900 dark:border-neutral-700 dark:bg-neutral-900 dark:focus:border-white dark:focus:ring-white"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium mb-2">
Message
</label>
<textarea
id="message"
name="message"
rows={5}
required
value={form.message}
onChange={handleChange}
className="w-full rounded-lg border border-neutral-300 px-4 py-2.5 text-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900 dark:border-neutral-700 dark:bg-neutral-900 dark:focus:border-white dark:focus:ring-white resize-none"
/>
</div>
<button
type="submit"
className="w-full rounded-lg bg-neutral-900 py-2.5 text-sm font-semibold text-white hover:bg-neutral-700 transition-colors dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200"
>
Envoyer le message
</button>
</form>
);
}
Chaque input a un <label> avec une paire htmlFor/id correspondante. C'est non-négociable pour l'accessibilité — cliquer sur le label doit focus l'input. L'attribut required fournit une validation native du navigateur avant la soumission du formulaire.
Principes de design de formulaire
Avant d'explorer d'autres patterns, voici les règles de design qui s'appliquent à tout formulaire de contact :
Label au-dessus du champ, pas à côté. Les labels alignés en haut se scannent plus vite que ceux alignés sur le côté. Les études d'eye-tracking montrent que les utilisateurs lisent les formulaires à labels en haut 50% plus rapidement.
Une seule colonne. Les layouts de formulaire à deux colonnes font que les utilisateurs ratent des champs. La seule exception est prénom / nom sur la même ligne, qui est un pattern universellement compris.
Le bouton doit décrire l'action. "Envoyer le message" est mieux que "Soumettre". "Demander un rappel" est mieux que "Envoyer". Le libellé doit correspondre à ce qui se passe ensuite.
Montrer un feedback de succès. Après soumission, remplacez le formulaire par un message de confirmation ou affichez une notification toast. Ne laissez jamais l'utilisateur se demander si le formulaire est bien passé.
2. Layout split : formulaire + infos
Le pattern de page contact le plus populaire pour les sites d'entreprise. La colonne gauche contient le formulaire ; la colonne droite contient les informations de contact (adresse, téléphone, email) et les horaires d'ouverture.
Utilisez grid md:grid-cols-2 gap-12 pour le layout. La colonne d'infos aide les visiteurs qui préfèrent l'email ou le téléphone au formulaire, et elle ajoute de la crédibilité en montrant une adresse physique.
Ajoutez des liens sociaux dans la colonne d'infos si l'entreprise a des comptes sociaux actifs. Cela donne aux visiteurs une troisième option de canal et signale que l'entreprise est accessible.
3. Formulaire de contact avec carte
Pour les entreprises avec des locaux physiques, intégrer une carte à côté du formulaire de contact fournit du contexte. La carte peut se placer au-dessus du formulaire (pleine largeur) ou à côté (layout split).
Utilisez une image de carte statique de Mapbox ou Google Maps Static API plutôt qu'un embed interactif. L'iframe Google Maps interactive charge 500 Ko+ de JavaScript, ce qui impacte la performance de la page. Une image statique avec un lien vers Google Maps offre la même valeur pour une fraction du coût.
Si vous avez vraiment besoin d'une carte interactive, chargez l'iframe en lazy pour qu'elle ne se charge que quand elle est visible :
<iframe
src={`https://www.google.com/maps/embed/v1/place?key=${apiKey}&q=${encodeURIComponent(address)}`}
className="h-64 w-full rounded-xl border-0"
loading="lazy"
allowFullScreen
/>
4. Labels flottants
Les labels flottants démarrent à l'intérieur du champ comme texte placeholder, puis s'animent vers le haut quand l'utilisateur focus ou tape. Cela économise de l'espace vertical tout en maintenant un labeling clair.
L'astuce CSS : utilisez les utilitaires peer et peer-placeholder-shown de Tailwind pour contrôler la position du label selon l'état de l'input :
<div className="relative">
<input
id="email"
name="email"
type="email"
placeholder=" "
required
className="peer w-full rounded-lg border border-neutral-300 px-4 pt-5 pb-2 text-sm focus:border-neutral-900 focus:outline-none focus:ring-1 focus:ring-neutral-900"
/>
<label
htmlFor="email"
className="absolute left-4 top-2 text-xs text-neutral-500 transition-all peer-placeholder-shown:top-3.5 peer-placeholder-shown:text-sm peer-focus:top-2 peer-focus:text-xs peer-focus:text-neutral-900"
>
Adresse email
</label>
</div>
Le placeholder=" " (un seul espace) est la clé. L'état peer-placeholder-shown est vrai quand le placeholder est visible (input vide et pas focusé), ce qui positionne le label comme placeholder. Quand l'utilisateur tape ou focus, le label flotte vers le haut.
Ce pattern est élégant mais a un compromis : le mouvement du label peut dérouter certains utilisateurs, et le texte du label plus petit en haut est plus difficile à lire pour les utilisateurs avec des déficiences visuelles. Utilisez-le pour les sites orientés design ; restez sur les labels standard en haut pour les formulaires où l'accessibilité est critique.
5. Formulaire multi-étapes
Pour les formulaires avec beaucoup de champs (candidatures, demandes détaillées, onboarding), un wizard multi-étapes réduit la charge cognitive en ne montrant que quelques champs à la fois.
Structurez le formulaire comme un tableau d'étapes, chacune avec ses propres champs et validation. Un indicateur de progression en haut montre l'étape actuelle :
const steps = [
{ title: "Vos infos", fields: ["name", "email", "phone"] },
{ title: "Détails du projet", fields: ["budget", "timeline", "description"] },
{ title: "Vérifier & envoyer", fields: [] },
];
Naviguez entre les étapes avec des boutons "Suivant" et "Retour". Validez les champs de l'étape courante avant de permettre à l'utilisateur de continuer. La dernière étape affiche un résumé de toutes les données saisies avec un bouton d'édition pour chaque section.
Limitez le nombre total d'étapes à trois ou quatre. Au-delà, cela crée de la fatigue de formulaire. Chaque étape doit prendre moins de 30 secondes à compléter.
Validation et états d'erreur
La validation côté client doit se faire au blur (quand l'utilisateur quitte un champ), pas au change (pendant qu'il tape encore). Montrer des erreurs en pleine frappe est frustrant.
Stylez les états d'erreur avec une bordure rouge et un message d'erreur sous le champ :
<input
className={`w-full rounded-lg border px-4 py-2.5 text-sm ${
error
? "border-red-500 focus:border-red-500 focus:ring-red-500"
: "border-neutral-300 focus:border-neutral-900 focus:ring-neutral-900"
}`}
/>
{error && <p className="mt-1 text-xs text-red-500">{error}</p>}
Utilisez aria-invalid="true" et aria-describedby pointant vers le message d'erreur pour le support des lecteurs d'écran. Les attributs natifs required, type="email" et minLength fournissent une première couche de validation gratuite.
Prévention du spam
Les formulaires de contact attirent les bots. Trois contre-mesures efficaces :
- Champ honeypot : ajoutez un champ caché. Les bots le remplissent ; les humains non. Rejetez les soumissions où le champ caché a une valeur.
- Vérification temporelle : enregistrez quand la page a été chargée. Rejetez les soumissions qui arrivent dans les 2 secondes — un humain ne peut pas remplir un formulaire aussi vite.
- reCAPTCHA v3 ou Turnstile : challenges invisibles qui scorent la soumission. Utilisez en dernier recours car ça ajoute une dépendance externe.
Sections contact pré-construites
Construire des formulaires accessibles et responsive avec validation, états d'erreur et prévention du spam prend du temps. Le catalogue contact d'Incubator propose 10+ sections de formulaire de contact prêtes à l'emploi — simple, split, avec carte, labels flottants, multi-étapes — le tout en React avec Tailwind CSS.
Explorez la bibliothèque de composants complète pour chaque section dont votre site a besoin, des sections FAQ aux témoignages.