---
type: how-to
title: Ejemplos de integración de frontend
summary: Monta el widget en HTML plano, React, Vue, Svelte, Angular y Next.js. El elemento es el mismo en todas partes.
---

El widget es un custom element estándar, así que funciona en cada framework sin un wrapper que instalar. La forma siempre es la misma: carga el paquete una vez ([integración del lado del cliente](/docs/integration-guides/cdn)), renderiza `<caputchin-widget sitekey="cpt_pub_...">` dentro de tu formulario, y lee el campo oculto `caputchin-token` que inyecta cuando el visitante supera el reto. Para un juego, cámbialo por `<caputchin-game ... game="caputchin/games/leaf-memory">`.

<Callout type="warn">
Si una clave de sitio tiene el gate de juego activado (su ajuste **Exigir un juego para verificar**), el `<caputchin-widget>` de checkbox no puede pasar en esa clave: el servidor exige un juego, así que el widget lanza un error `gate-unavailable`. Usa `<caputchin-game>` en cualquier clave con gate. Esto es a propósito, porque el gate se impone del lado de Caputchin y una checkbox no puede satisfacerlo. Mira [el gate de juego](/docs/site-keys/games).
</Callout>

Los ejemplos de abajo difieren solo en la sintaxis de cada framework y en su única peculiaridad de custom element.

## HTML plano

El campo inyectado viaja junto a un POST de formulario normal, así que no hay nada que cablear:

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

Importa una vez al arranque, luego lee el campo de `FormData` al enviar:

```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 usas TypeScript con React, declara ambos elementos una vez para que JSX los acepte:

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

Esta forma `declare module "react"` es específica del JSX de React. En TypeScript plano los elementos son solo `HTMLElement` y no necesitan declaración, y los demás frameworks de abajo tipan los custom elements a su manera, así que solo necesitas este bloque en un proyecto de React.

## Vue

Dile al compilador de Vue que las etiquetas `caputchin-` son custom elements, para que no intente resolverlas como componentes:

```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 renderiza los custom elements tal cual, sin configuración necesaria:

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

Añade `CUSTOM_ELEMENTS_SCHEMA` al módulo o al componente standalone para que Angular permita la etiqueta desconocida, e importa el paquete una vez (en `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

El mismo código de React funciona. El custom element se renderiza como una etiqueta vacía durante el render en el servidor y se actualiza en el cliente una vez que el paquete carga, así que no hace falta ningún tratamiento especial más allá de importar `@caputchin/widget` en un componente de cliente (o cargar el script del CDN con `next/script`).

## Véase también

- [Integración del lado del cliente](/docs/integration-guides/cdn) para las opciones de carga.
- [Integración del lado del servidor](/docs/integration-guides/verify-on-your-backend) para la mitad de backend.
