---
type: how-to
title: Exemples d'intégration frontend
summary: Monte le widget en HTML pur, React, Vue, Svelte, Angular et Next.js. L'élément est le même partout.
---

Le widget est un élément personnalisé standard, donc il fonctionne dans chaque framework sans wrapper à installer. La forme est toujours la même : charge le paquet une fois ([intégration côté client](/docs/integration-guides/cdn)), affiche `<caputchin-widget sitekey="cpt_pub_...">` dans ton formulaire, et lis le champ caché `caputchin-token` qu'il injecte quand le visiteur réussit l'épreuve. Pour un jeu, remplace par `<caputchin-game ... game="caputchin/games/leaf-memory">`.

<Callout type="warn">
Si une clé de site a le gate de jeu activé (son réglage **Exiger un jeu pour vérifier**), le `<caputchin-widget>` à case à cocher ne peut pas passer sur cette clé : le serveur exige un jeu, donc le widget lève une erreur `gate-unavailable`. Utilise `<caputchin-game>` sur toute clé gatée. C'est voulu, parce que le gate est appliqué du côté de Caputchin et une case à cocher ne peut pas le satisfaire. Vois [le gate de jeu](/docs/site-keys/games).
</Callout>

Les exemples ci-dessous ne diffèrent que par la syntaxe de chaque framework et son unique particularité d'élément personnalisé.

## HTML pur

Le champ injecté voyage avec un POST de formulaire normal, donc il n'y a rien à câbler :

```html
<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@3/dist/widget.js"></script>

<form action="/contact" method="POST">
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>
```

## React

Importe une fois au démarrage, puis lis le champ depuis `FormData` à l'envoi :

```jsx
import "@caputchin/widget";

export function ContactForm() {
  async function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    await fetch("/api/contact", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ email: data.get("email"), token: data.get("caputchin-token") }),
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..." />
      <button type="submit">Send</button>
    </form>
  );
}
```

Si tu utilises TypeScript avec React, déclare les deux éléments une fois pour que JSX les accepte :

```ts
// caputchin.d.ts
import type { DetailedHTMLProps, HTMLAttributes } from "react";
declare module "react" {
  namespace JSX {
    interface IntrinsicElements {
      "caputchin-widget": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string }, HTMLElement>;
      "caputchin-game": DetailedHTMLProps<HTMLAttributes<HTMLElement> & { sitekey: string; game?: string; games?: string }, HTMLElement>;
    }
  }
}
```

Cette forme `declare module "react"` est propre au JSX de React. En TypeScript pur, les éléments sont simplement des `HTMLElement` et n'ont besoin d'aucune déclaration, et les autres frameworks ci-dessous typent les éléments personnalisés à leur façon, donc tu n'as besoin de ce bloc que dans un projet React.

## Vue

Indique au compilateur de Vue que les balises `caputchin-` sont des éléments personnalisés, pour qu'il n'essaie pas de les résoudre comme des composants :

```js
// vite.config.js
export default {
  plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag.startsWith("caputchin-") } } })],
};
```

```vue
<script setup>
import "@caputchin/widget";
function onSubmit(e) {
  const data = new FormData(e.target);
  // send data.get("caputchin-token") to your backend
}
</script>

<template>
  <form @submit.prevent="onSubmit">
    <input name="email" type="email" required />
    <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
    <button type="submit">Send</button>
  </form>
</template>
```

## Svelte

Svelte affiche les éléments personnalisés tels quels, sans config :

```svelte
<script>
  import "@caputchin/widget";
  function onSubmit(e) {
    const data = new FormData(e.currentTarget);
    // send data.get("caputchin-token") to your backend
  }
</script>

<form on:submit|preventDefault={onSubmit}>
  <input name="email" type="email" required />
  <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
  <button type="submit">Send</button>
</form>
```

## Angular

Ajoute `CUSTOM_ELEMENTS_SCHEMA` au module ou au composant standalone pour qu'Angular autorise la balise inconnue, et importe le paquet une fois (dans `main.ts`) :

```ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@caputchin/widget";

@Component({
  selector: "contact-form",
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <form (submit)="onSubmit($event)">
      <input name="email" type="email" required />
      <caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
      <button type="submit">Send</button>
    </form>
  `,
})
export class ContactFormComponent {
  onSubmit(e: Event) {
    const data = new FormData(e.target as HTMLFormElement);
    // send data.get("caputchin-token") to your backend
  }
}
```

## Next.js

Le même code React fonctionne. L'élément personnalisé s'affiche comme une balise vide pendant le rendu serveur et se met à niveau côté client une fois le paquet chargé, donc aucun traitement particulier n'est nécessaire au-delà d'importer `@caputchin/widget` dans un composant client (ou de charger le script CDN avec `next/script`).

## Voir aussi

- [Intégration côté client](/docs/integration-guides/cdn) pour les options de chargement.
- [Intégration côté serveur](/docs/integration-guides/verify-on-your-backend) pour la moitié backend.
