CAPTCHA
Bot protection with Cloudflare Turnstile, Google reCAPTCHA, hCaptcha, or Yandex SmartCaptcha
Full example
The complete sandbox example, read directly from the form-develop-app source at build time —
switch framework/skin to see Form.Captcha wired to a real form. No shadcn/Vue/Angular
equivalent exists yet — those tabs show as disabled.
'use client'
import { Box, Code, Heading, Text, VStack } from '@chakra-ui/react'
import { DemoPageLayout } from '../_components'
export default function CaptchaDemoPage() {
return (
<DemoPageLayout
title="Form.Captcha"
description="CAPTCHA виджет — Turnstile, reCAPTCHA, hCaptcha, Yandex SmartCaptcha"
>
<VStack gap={8} align="stretch">
<Box>
<Heading size="md" mb={3}>
Использование
</Heading>
<Text fontSize="sm" color="fg.muted" mb={4}>
CAPTCHA требует настоящий siteKey от провайдера. Ниже — API и примеры конфигурации.
</Text>
<Code display="block" whiteSpace="pre" fontSize="xs" p={4} borderRadius="md">
{`// В createForm (один раз для приложения)
const AppForm = createForm({
captcha: {
provider: 'turnstile',
siteKey: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!,
theme: 'auto',
},
})
// В форме
<AppForm onSubmit={handleSubmit}>
<AppForm.Field.String name="email" label="Email" />
<AppForm.Captcha />
<AppForm.Button.Submit>Отправить</AppForm.Button.Submit>
</AppForm>
// Серверная верификация
import { verifyCaptcha } from '@letar/forms/captcha/server'
const result = await verifyCaptcha(token, {
provider: 'turnstile',
secretKey: process.env.TURNSTILE_SECRET_KEY!,
})`}
</Code>
</Box>
<Box>
<Heading size="md" mb={3}>
Провайдеры
</Heading>
<VStack align="start" gap={2} fontSize="sm">
<Text>
<strong>turnstile</strong> — Cloudflare Turnstile (рекомендуемый, бесплатный)
</Text>
<Text>
<strong>recaptcha</strong> — Google reCAPTCHA v2/v3
</Text>
<Text>
<strong>hcaptcha</strong> — hCaptcha
</Text>
<Text>
<strong>smartcaptcha</strong> — Yandex SmartCaptcha (данные обрабатываются в РФ, для 152-ФЗ)
</Text>
</VStack>
</Box>
</VStack>
</DemoPageLayout>
)
}shadcn-вариант этого примера ещё не готов.
Vue-пример для этого поля появится позже.
Angular-пример для этого поля появится позже.
Overview
Form.Captcha adds bot protection to forms. Supports four providers: Cloudflare Turnstile (recommended, free), Google reCAPTCHA, hCaptcha, and Yandex SmartCaptcha (data stays in Russia — useful for 152-FZ compliance).
Setup
Configure CAPTCHA once in createForm:
const AppForm = createForm({
captcha: {
provider: 'turnstile',
siteKey: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY!,
theme: 'auto',
},
})Usage in Form
<AppForm onSubmit={handleSubmit}>
<AppForm.Field.String name="email" label="Email" />
<AppForm.Field.Textarea name="message" label="Message" />
<AppForm.Captcha />
<AppForm.Button.Submit>Send</AppForm.Button.Submit>
</AppForm>The CAPTCHA token is automatically included in the form data as __captchaToken.
Server Verification
import { verifyCaptcha } from '@letar/forms/captcha/server'
export async function submitAction(data: FormData) {
const token = data.__captchaToken
const result = await verifyCaptcha(token, {
provider: 'turnstile',
secretKey: process.env.TURNSTILE_SECRET_KEY!,
})
if (!result.success) {
throw new Error('CAPTCHA verification failed')
}
// Process form...
}Providers
| Provider | Free | Invisible | Notes |
|---|---|---|---|
| Turnstile | Yes | Yes | Recommended, privacy-friendly |
| reCAPTCHA | Partial | v3 only | Google, most popular |
| hCaptcha | Partial | Yes | Privacy alternative to reCAPTCHA |
| SmartCaptcha | Yes | Yes | Yandex, data processed in Russia (152-FZ). No theme support. |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
provider | 'turnstile' | 'recaptcha' | 'hcaptcha' | 'smartcaptcha' | from createForm | Override provider |
siteKey | string | from createForm | Override siteKey |
theme | 'auto' | 'light' | 'dark' | 'auto' | Widget theme |
size | 'normal' | 'compact' | 'invisible' | 'normal' | Widget size |
language | string | — | Language code (ISO 639-1) |
onSuccess | (token: string) => void | — | Success callback |
onError | (error) => void | — | Error callback |
onExpire | () => void | — | Token expiry callback |
Live Example
Try the interactive example on forms-example.letar.best.