Se rendre au contenu

Welcome .


Sign u

Cette question a été signalée

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>


Ignorer
Publications associées Réponses Vues Activité
0
août 25
108
0
août 25
58
0
août 25
3
0
août 25
4
0
août 25
104