Ce code fournit un formulaire de contact complet, accessible et responsive, que vous pouvez intégrer directement à votre site web. Il est conçu pour envoyer les messages saisis par les visiteurs directement à l’adresse e-mail du créateur du formulaire via le service Formspree (ou tout autre backend compatible). Le formulaire inclut une validation côté client, un système anti-spam discret (honeypot), des styles modernes en pur CSS, et des messages de retour utilisateur clairs pour chaque étape de l’envoi.
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Formulaire de contact — Plug & Play</title>
<!--
OBJECTIF
========
Un formulaire "plug-and-play" qui envoie un email au créateur du formulaire (vous).
MODE SANS BACKEND (recommandé pour aller vite)
----------------------------------------------
1) Créez un formulaire sur Formspree (gratuit) pour obtenir un ID de type f/abcdwxyz.
https://formspree.io → New form → Copier l'ID.
2) Remplacez YOUR_FORMSPREE_ID ci-dessous dans la constante FORMSPREE_ENDPOINT.
3) Déployez tel quel : le mail arrivera à l'adresse configurée sur Formspree.
⚠️ Remarque : le destinataire de l'email est défini côté Formspree (votre email vérifié).
ALTERNATIVE BACKEND (auto‑hébergé)
----------------------------------
Si vous préférez ne dépendre d'aucun service, implémentez un endpoint /api/contact
qui envoie un email (Nodemailer/SMTP) et remplacez FORMSPREE_ENDPOINT par votre URL.
ACCESSIBILITÉ & SÉCURITÉ
------------------------
- Champs labellisés, messages d'erreur descriptifs.
- Validation HTML5 + validation JS.
- Anti‑spam simple (champ honeypot caché).
- Aucune dépendance externe.
-->
<style>
:root {
--radius: 14px;
--border: #e5e7eb;
--text: #111827;
--muted: #6b7280;
--primary: #4f46e5;
--bg: #ffffff;
--ok: #10b981;
--error: #ef4444;
}
* { box-sizing: border-box; }
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; color: var(--text); background: #f8fafc; margin: 0; padding: 24px; }
.card { max-width: 720px; margin: 0 auto; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: 0 1px 2px rgba(0,0,0,.04); }
.header { padding: 20px 24px; border-bottom: 1px solid var(--border); }
h1 { font-size: 1.25rem; margin: 0 0 4px; }
p.sub { margin: 0; color: var(--muted); font-size: .95rem; }
form { display: grid; gap: 14px; padding: 20px 24px 24px; }
label { font-weight: 600; font-size: .95rem; }
input, textarea { width: 100%; padding: 12px 14px; border: 1px solid var(--border); border-radius: 12px; font: inherit; background: #fff; }
input:focus, textarea:focus { outline: 3px solid rgba(79,70,229,.2); border-color: var(--primary); }
.row { display: grid; gap: 14px; grid-template-columns: 1fr 1fr; }
.row > div { display: grid; gap: 6px; }
.full { display: grid; gap: 6px; }
.hint { color: var(--muted); font-size: .9rem; }
.actions { display: flex; gap: 10px; align-items: center; }
button { appearance: none; border: 1px solid transparent; background: var(--primary); color: #fff; padding: 12px 16px; border-radius: 10px; font-weight: 700; cursor: pointer; }
button[disabled] { opacity: .6; cursor: not-allowed; }
.status { font-size: .95rem; }
.ok { color: var(--ok); }
.err { color: var(--error); }
/* honeypot (anti‑bot) */
.hp { position: absolute; left: -5000px; width: 1px; height: 1px; overflow: hidden; }
</style>
</head>
<body>
<div class="card" role="region" aria-labelledby="title">
<div class="header">
<h1 id="title">Contact</h1>
<p class="sub">Remplissez le formulaire — vous recevrez un email (côté créateur du formulaire) à chaque envoi.</p>
</div>
<!-- FORMULAIRE -->
<form id="contact-form" novalidate>
<div class="row">
<div>
<label for="name">Nom</label>
<input id="name" name="name" type="text" placeholder="Votre nom" autocomplete="name" required />
</div>
<div>
<label for="email">Email</label>
<input id="email" name="email" type="email" placeholder="vous@domaine.com" autocomplete="email" required />
</div>
</div>
<div class="full">
<label for="subject">Sujet</label>
<input id="subject" name="subject" type="text" placeholder="Sujet du message" required />
</div>
<div class="full">
<label for="message">Message</label>
<textarea id="message" name="message" rows="6" placeholder="Votre message…" required></textarea>
<div class="hint">En envoyant, vous acceptez que vos informations soient utilisées pour répondre à votre demande.</div>
</div>
<!-- Champ honeypot anti‑spam (ne pas remplir) -->
<div class="hp" aria-hidden="true">
<label for="website">Laissez ce champ vide</label>
<input id="website" name="website" type="text" tabindex="-1" autocomplete="off" />
</div>
<div class="actions">
<button type="submit" id="sendBtn">Envoyer</button>
<span class="status" id="status" aria-live="polite"></span>
</div>
</form>
</div>
<script>
// 1) REMPLACEZ par votre endpoint Formspree OU votre propre API
const FORMSPREE_ENDPOINT = "https://formspree.io/f/YOUR_FORMSPREE_ID"; // ← ex: https://formspree.io/f/mwkgjzya
const form = document.getElementById('contact-form');
const statusEl = document.getElementById('status');
const sendBtn = document.getElementById('sendBtn');
/** Validation simple côté client */
function validEmail(v){ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); }
form.addEventListener('submit', async (e) => {
e.preventDefault();
statusEl.textContent = '';
const data = new FormData(form);
// honeypot → si rempli, on bloque
if ((data.get('website')||'').trim() !== '') {
statusEl.textContent = 'Envoi bloqué.';
statusEl.className = 'status err';
return;
}
// Checks de base
const name = (data.get('name')||'').trim();
const email = (data.get('email')||'').trim();
const subject = (data.get('subject')||'').trim();
const message = (data.get('message')||'').trim();
if (!name || !email || !subject || !message) {
statusEl.textContent = 'Veuillez remplir tous les champs requis.';
statusEl.className = 'status err';
return;
}
if (!validEmail(email)) {
statusEl.textContent = 'Adresse email invalide.';
statusEl.className = 'status err';
return;
}
sendBtn.disabled = true;
sendBtn.textContent = 'Envoi…';
try {
// Si vous utilisez Formspree, vous pouvez ajouter des métadonnées :
// - _replyto → permet de répondre directement à l'expéditeur
// - _subject → sujet de l'email reçu côté créateur
// - _gotcha → second honeypot possible
data.append('_replyto', email);
data.append('_subject', subject || 'Nouveau message via formulaire');
const res = await fetch(FORMSPREE_ENDPOINT, { method: 'POST', body: data, headers: { 'Accept': 'application/json' } });
if (res.ok) {
form.reset();
statusEl.textContent = 'Message envoyé. Merci !';
statusEl.className = 'status ok';
} else {
const info = await res.json().catch(() => ({}));
const msg = (info && info.error) || 'Une erreur est survenue lors de l\'envoi.';
statusEl.textContent = msg;
statusEl.className = 'status err';
}
} catch (err) {
statusEl.textContent = 'Réseau indisponible. Réessayez plus tard.';
statusEl.className = 'status err';
} finally {
sendBtn.disabled = false;
sendBtn.textContent = 'Envoyer';
}
});
</script>
</body>
</html>