---
type: how-to
title: Frontend-Integrationsbeispiele
summary: Bind das Widget in reinem HTML, React, Vue, Svelte, Angular und Next.js ein. Das Element ist überall dasselbe.
---

Das Widget ist ein standardmäßiges Custom Element, also funktioniert es in jedem Framework ohne Wrapper zum Installieren. Die Form ist immer dieselbe: lad das Paket einmal ([clientseitige Integration](/docs/integration-guides/cdn)), render `<caputchin-widget sitekey="cpt_pub_...">` in deinem Formular und lies das versteckte `caputchin-token`-Feld aus, das es einfügt, wenn der Besucher die Aufgabe löst. Für ein Spiel tausch `<caputchin-game ... game="caputchin/games/leaf-memory">` ein.

<Callout type="warn">
Hat ein Site-Key das Spiel-Gate eingeschaltet (seine Einstellung **Ein Spiel zur Verifizierung verlangen**), kann das Checkbox-`<caputchin-widget>` auf diesem Key nicht durchkommen: Der Server verlangt ein Spiel, also wirft das Widget einen `gate-unavailable`-Fehler. Nimm `<caputchin-game>` auf jedem gegateten Key. Das ist Absicht, weil das Gate auf Caputchins Seite durchgesetzt wird und eine Checkbox es nicht erfüllen kann. Sieh dir [das Spiel-Gate](/docs/site-keys/games) an.
</Callout>

Die Beispiele unten unterscheiden sich nur in der Syntax jedes Frameworks und seiner einen Custom-Element-Eigenheit.

## Reines HTML

Das eingefügte Feld reitet mit einem normalen Formular-POST mit, also gibt es nichts zu verdrahten:

```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

Importier einmal beim Start, dann lies das Feld beim Absenden aus `FormData`:

```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>
  );
}
```

Wenn du TypeScript mit React nutzt, deklarier beide Elemente einmal, damit JSX sie akzeptiert:

```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>;
    }
  }
}
```

Diese `declare module "react"`-Form ist spezifisch für Reacts JSX. In reinem TypeScript sind die Elemente einfach `HTMLElement` und brauchen keine Deklaration, und die anderen Frameworks unten typisieren Custom Elements auf ihre eigene Weise, also brauchst du diesen Block nur in einem React-Projekt.

## Vue

Sag Vues Compiler, dass `caputchin-`-Tags Custom Elements sind, damit er nicht versucht, sie als Komponenten aufzulösen:

```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 rendert Custom Elements wie sie sind, keine Konfiguration nötig:

```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

Füg `CUSTOM_ELEMENTS_SCHEMA` zum Modul oder zur Standalone-Komponente hinzu, damit Angular das unbekannte Tag erlaubt, und importier das Paket einmal (in `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

Derselbe React-Code funktioniert. Das Custom Element rendert beim Server-Rendering als leeres Tag und wird auf dem Client aktualisiert, sobald das Paket lädt, also ist über das Importieren von `@caputchin/widget` in einer Client-Komponente hinaus nichts Besonderes nötig (oder lad das CDN-Script mit `next/script`).

## Siehe auch

- [Clientseitige Integration](/docs/integration-guides/cdn) für die Lade-Optionen.
- [Serverseitige Integration](/docs/integration-guides/verify-on-your-backend) für die Backend-Hälfte.
