# Comment ajouter un formulaire de liste d'attente en HTML simple à n'importe quelle landing page

> Une page de liste d'attente complète en HTML simple, sans build, qui fonctionne sans JavaScript, remercie sur place quand il se charge, et se replie si besoin.

Publié le 2026-10-25 · https://ikiform.com/fr/guides/waitlist/html

Écrivez un formulaire HTML normal qui envoie l'e-mail à votre endpoint Ikiform et redirige vers une page de remerciement : il fonctionne ainsi sur n'importe quel hébergeur sans aucun JavaScript. Ajoutez ensuite un petit script qui envoie le même formulaire avec `fetch` et affiche le remerciement sur place, et qui rend la main à l'envoi classique chaque fois qu'il ne peut pas aller au bout, par exemple quand la vérification humaine est activée.

Une page de liste d'attente n'a besoin ni de framework, ni d'étape de build, ni de serveur. Quatre fichiers statiques et un backend de formulaires suffisent, et la page se charge plus vite que tout ce qu'exporte un créateur de sites. Cette page construit l'ensemble, couche par couche, pour que chaque couche fonctionne seule.

## Ce qui compte sans étape de build

- **Le formulaire est l'intégration.** Un simple `<form>` avec une `action` constitue toute la connexion à Ikiform. Pas de package, pas de clé d'API, rien à compiler.
- **JavaScript est une amélioration, pas une obligation.** La page doit collecter des inscriptions quand le script est bloqué, lent ou cassé. Chaque couche ci-dessous s'ajoute à la précédente et se replie sur elle.
- **Le script ne doit jamais bloquer quelqu'un.** Si `fetch` échoue ou si Ikiform demande une vérification humaine que `fetch` ne peut pas exécuter, le script rend le formulaire au navigateur pour un envoi classique.
- **L'hébergement peut être n'importe où.** GitHub Pages, Netlify, Cloudflare Pages, un bucket S3 ou un hébergement mutualisé servent tous ces fichiers tels quels.

| Couche | Ce qu'elle ajoute | Si elle manque |
| --- | --- | --- |
| Formulaire HTML | Envoie l'e-mail, redirige vers `thanks.html` | Rien ne fonctionne, elle passe donc en premier |
| CSS | Une page lisible en mode clair et sombre | Un formulaire sobre mais fonctionnel |
| Script | Remerciement sur place, erreurs de champ, pas de changement de page | L'envoi classique et la redirection |

Schéma : Les trois chemins possibles d'une inscription sur une page de liste d'attente en HTML simple

1. Avec JavaScript désactivé, le navigateur envoie le formulaire, Ikiform redirige en 303 vers votre URL _redirect, et le visiteur voit thanks.html.
2. Avec le script chargé, il envoie avec fetch et demande du JSON. Ikiform répond 201 ou une erreur, et le script affiche le résultat sur la même page.
3. Si fetch échoue, ou si Ikiform répond challenge_required, le script appelle form.submit().
4. Cet envoi classique passe par la page de vérification humaine d'Ikiform si besoin et se termine sur thanks.html.

Chaque chemin aboutit à une inscription stockée. Le script ne change que la façon dont le visiteur la voit.

## Les fichiers

### La page

`index.html`:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Acme is almost here</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <h1>Acme is almost here</h1>
    <p>Invoices that chase themselves. Leave your email and we'll invite you first.</p>

    <form id="waitlist" action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
      <label for="email">Email</label>
      <div class="row">
        <input id="email" name="email" type="email" autocomplete="email" required placeholder="you@company.com">
        <button>Join the waitlist</button>
      </div>
      <input type="hidden" name="_redirect" value="https://example.com/thanks.html">
      <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden>
      <p class="message" role="status" aria-live="polite"></p>
    </form>
  </main>
  <script src="waitlist.js" defer></script>
</body>
</html>
```

- `_redirect` envoie le navigateur vers `thanks.html` après un envoi classique. Il ne fonctionne que pour une URL `https:` sur l'un des **Allowed domains** du formulaire : ajoutez-y donc votre domaine. **Redirect after submit** dans les réglages du formulaire fait la même chose sans l'input masqué.
- L'input `_gotcha` est le honeypot. Les humains ne le voient jamais ; les bots qui remplissent tous les inputs sont stockés comme spam.
- `aria-live="polite"` fait annoncer les messages du script par les lecteurs d'écran sans déplacer le focus.
- La balise script utilise `defer` : elle s'exécute après la création du formulaire et ne bloque jamais l'affichage de la page.

### Le script d'amélioration

`waitlist.js`:

```js
const form = document.getElementById("waitlist");
const message = form.querySelector(".message");
const button = form.querySelector("button");
const key = crypto.randomUUID();

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  button.disabled = true;
  message.textContent = "Joining…";
  try {
    const response = await fetch(form.action, {
      method: "POST",
      headers: { Accept: "application/json", "Idempotency-Key": key },
      body: new FormData(form),
    });
    const result = await response.json();
    if (result.ok) {
      form.replaceChildren(Object.assign(document.createElement("p"), {
        textContent: "You're on the list. We'll email you on launch day.",
        role: "status",
      }));
      return;
    }
    if (result.error.code === "challenge_required") {
      form.submit();
      return;
    }
    const wait = response.headers.get("Retry-After");
    message.textContent = wait
      ? `Too many tries. Wait ${wait} seconds and try again.`
      : result.error.fields?.email?.[0] ?? result.error.message;
  } catch {
    form.submit();
    return;
  }
  button.disabled = false;
});
```

- `Accept: application/json` fait répondre Ikiform en JSON au lieu d'une redirection, pour que le script puisse afficher le résultat sur place.
- La clé d'idempotence est créée une fois par chargement de page. Un double clic, ou une nouvelle tentative après une réponse lente, ne stocke qu'une inscription.
- `challenge_required` signifie que la vérification anti-spam du formulaire est réglée sur **Ikiform** et que `fetch` n'a envoyé aucun jeton. `form.submit()` envoie de façon classique, ce qui affiche la page de vérification d'Ikiform puis la page de remerciement. Il ne déclenche pas à nouveau l'événement `submit`, il n'y a donc pas de boucle.
- Une erreur réseau prend le même repli. Si Ikiform était injoignable, l'envoi classique affiche l'erreur du navigateur, ce qui reste plus honnête qu'un bouton bloqué.
- Un `429` lit `Retry-After` et indique combien de temps attendre.

### La page de remerciement

`thanks.html`:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>You're on the list</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <h1>You're on the list</h1>
    <p>We'll email you the day Acme opens. Nothing before that.</p>
    <p><a href="/">Back to the page</a></p>
  </main>
</body>
</html>
```

### Les styles

`style.css`:

```text
body { margin: 0; font: 16px/1.5 system-ui, sans-serif; color: #111; background: #fff; }
main { max-width: 34rem; margin: 0 auto; padding: 18vh 1rem 4rem; }
h1 { font-size: 2.25rem; line-height: 1.1; margin: 0 0 0.75rem; }
label { display: block; font-weight: 600; margin: 1.5rem 0 0.375rem; }
.row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
input[type="email"] { flex: 1 1 14rem; font: inherit; padding: 0.625rem 0.75rem; border: 1px solid #ccc; border-radius: 0.5rem; }
button { font: inherit; padding: 0.625rem 1rem; border: 0; border-radius: 0.5rem; background: #111; color: #fff; cursor: pointer; }
button:disabled { opacity: 0.6; cursor: progress; }
.message { min-height: 1.5em; margin: 0.5rem 0 0; }
@media (prefers-color-scheme: dark) {
  body { color: #eee; background: #111; }
  input[type="email"] { background: #1b1b1b; color: inherit; border-color: #444; }
  button { background: #eee; color: #111; }
}
```

> Si la page est sur un autre domaine qu'`example.com`, mettez à jour `_redirect` et le lien de retour. Ikiform ignore un `_redirect` qu'il ne peut pas suivre et affiche sa propre page de remerciement hébergée : une incohérence ne fait donc jamais perdre d'inscription.

## Déployer et tester

Placez les quatre fichiers dans un dossier et téléversez-le sur n'importe quel hébergeur statique. Ajoutez ensuite le domaine de l'hébergeur dans **Allowed domains** : par exemple `username.github.io`, `your-site.netlify.app` ou votre propre domaine. Pendant le travail en local, ajoutez aussi `localhost` ; servez le dossier avec n'importe quel serveur statique, car une page ouverte via une URL `file://` n'envoie pas d'origine.

## Vérifiez que tout fonctionne

1. Dans **Allowed domains** du formulaire, ajoutez le domaine de votre site, plus `localhost` pendant le développement. Laissez vide pour accepter les envois de partout.
2. Désactivez JavaScript dans le navigateur et inscrivez-vous. Vous arrivez sur `thanks.html`.
3. Réactivez-le et inscrivez-vous à nouveau. Le formulaire est remplacé par la phrase de remerciement sans quitter la page.
4. Réglez **Spam check** sur **Ikiform** et inscrivez-vous une fois de plus. La page passe par la vérification, puis par `thanks.html`.
5. Ouvrez **Submissions** dans le tableau de bord. La nouvelle soumission y est, avec tous les champs envoyés.
6. Envoyez avec le honeypot rempli, `_gotcha=bot`. L'envoi reçoit quand même une réponse de succès, mais la soumission est stockée comme spam et n'envoie ni e-mail ni webhook.

## Pièges

| Problème | Que faire |
| --- | --- |
| Le navigateur arrive sur la page de remerciement d'Ikiform au lieu de la vôtre | `_redirect` est ignoré sauf si son domaine figure dans **Allowed domains** et qu'il s'agit d'une URL `https:`. Ajoutez le domaine, ou définissez plutôt **Redirect after submit** dans les réglages du formulaire. |
| `403 origin_not_allowed`, ou tous les envois finissent en spam | Le domaine de la page n'est pas dans les **Allowed domains** du formulaire. Ajoutez-le, plus `localhost` pour les tests en local. Les sous-domaines d'un domaine listé sont autorisés. |
| Les envois depuis une page ouverte comme fichier sont refusés ou marqués comme spam | Une page `file://` n'envoie pas d'origine. Servez le dossier avec un serveur statique local et ajoutez `localhost` aux **Allowed domains**. |
| La phrase de remerciement ne s'affiche jamais, mais les inscriptions arrivent | Le script ne s'est pas chargé : vérifiez le chemin dans l'attribut `src`, ou que l'hébergeur sert bien les fichiers `.js`. L'envoi classique fonctionne toujours, c'est pourquoi les inscriptions arrivent. |
| `402 test_limit_reached` | L'espace de travail n'a pas d'offre et a utilisé ses 50 tests gratuits depuis `localhost` ce mois-ci. Choisissez une offre, ou utilisez **Send test** dans le tableau de bord. |

La [documentation des formulaires HTML](https://ikiform.com/fr/docs/html.md) liste tous les champs réservés, et le [modèle de liste d'attente](https://ikiform.com/forms/waitlist) génère une version avec nom et rôle. L'export de la liste et l'e-mail de lancement sont dans le [guide principal](https://ikiform.com/fr/guides/waitlist.md#launch-day).

## Questions

### Un formulaire de liste d'attente peut-il fonctionner sans JavaScript ?

Oui. Un formulaire HTML simple envoie l'e-mail à votre endpoint Ikiform, et Ikiform redirige le navigateur vers votre page de remerciement. JavaScript n'ajoute que le message sur place.

### Où héberger une page de liste d'attente en HTML simple ?

N'importe où on sert des fichiers statiques : GitHub Pages, Netlify, Cloudflare Pages, S3 ou un hébergement mutualisé. Le formulaire envoie à Ikiform, l'hébergeur n'exécute donc jamais de code.

### Que se passe-t-il si la vérification humaine est activée et que le script envoie avec fetch ?

Ikiform répond 403 challenge_required. Le script de ce guide le détecte et envoie le formulaire de façon classique, ce qui affiche la page de vérification puis votre page de remerciement.

### Ikiform héberge-t-il la page du formulaire pour moi ?

Non. Ikiform n'a pas de pages de formulaire hébergées. Vous placez le formulaire sur votre propre site, dans n'importe quel créateur de sites ou framework, et le faites pointer vers votre endpoint Ikiform.

### Ikiform est-il gratuit ?

Non, il n'y a pas d'offre gratuite. Les tests sont gratuits : vous pouvez envoyer 50 soumissions de test par mois depuis localhost avant de payer. Pour passer en production, Pro commence à 6 $/mois pour 500 soumissions, ou vous pouvez payer une seule fois 10 000 soumissions pour 49 $ ou 50 000 soumissions pour 149 $, avec des crédits qui n'expirent jamais.

## Autres guides

- [Liste d'attente pour un nouveau SaaS](https://ikiform.com/fr/guides/waitlist.md): Créez une liste d'attente avant lancement sur votre landing page : le formulaire, l'anti-spam, les doublons, les e-mails de confirmation et un export propre le jour J.
- [Liste d'attente Next.js](https://ikiform.com/fr/guides/waitlist/nextjs.md): Une liste d'attente Next.js App Router : une server action qui transmet avec une clé secrète, useActionState, un merci optimiste et une limite par visiteur.
- [Liste d'attente sur Carrd](https://ikiform.com/fr/guides/waitlist/carrd.md): Collectez des inscriptions sur une page Carrd : un formulaire Send to URL sur Pro Plus avec suivi UTM, ou un formulaire HTML intégré avec honeypot sur Pro Standard.
- [Liste de lancement Product Hunt](https://ikiform.com/fr/guides/waitlist/product-hunt.md): Collectez une liste des semaines avant Product Hunt, taguez chaque inscription par canal et envoyez l'e-mail du jour J en deux vagues calées sur l'heure du Pacifique.
- [Inscription bêta avec sélection](https://ikiform.com/fr/guides/waitlist/beta-signup.md): Un formulaire de candidature bêta avec des questions de sélection appliquées côté serveur, et un script qui note les candidats et choisit chaque cohorte.
- [Liste d'attente d'application mobile](https://ikiform.com/fr/guides/waitlist/mobile-app.md): Une liste d'attente avant lancement pour une app iOS ou Android qui demande le téléphone utilisé, envoie les testeurs iPhone vers TestFlight et remplit un test fermé Play.
