con un clic
basic-setup
// Install and render your first Turnstile widget with required configuration. Activate this skill when starting a new project with react-turnstile or when implementing basic CAPTCHA protection for the first time.
// Install and render your first Turnstile widget with required configuration. Activate this skill when starting a new project with react-turnstile or when implementing basic CAPTCHA protection for the first time.
Handle multiple Turnstile widgets on the same page without conflicts. Activate when adding more than one CAPTCHA to a page, or when widgets interfere with each other.
Integrate Turnstile with Next.js, handle SSR, hydration, and App Router. Activate when building Next.js applications with App Router or Pages Router, or when encountering SSR-related errors.
Handle token generation, expiration, validation workflow, and form integration. Activate when implementing form submission with CAPTCHA, handling token expiration, or integrating with server-side validation.
Customize widget appearance, theme, execution mode, and behavior. Activate when styling the widget, changing when/how tokens are generated, or integrating with specific UX flows like multi-step forms.
| name | basic-setup |
| description | Install and render your first Turnstile widget with required configuration. Activate this skill when starting a new project with react-turnstile or when implementing basic CAPTCHA protection for the first time. |
| triggers | ["install turnstile react","add captcha to form","setup cloudflare turnstile","basic turnstile example","how to use @marsidev/react-turnstile","turnstile widget not showing"] |
| category | core |
| metadata | {"library":"@marsidev/react-turnstile","library_version":"1.4.2","framework":"React"} |
Install and render a Cloudflare Turnstile widget in your React application.
# npm
npm install @marsidev/react-turnstile
# pnpm
pnpm add @marsidev/react-turnstile
# yarn
yarn add @marsidev/react-turnstile
# bun
bun add @marsidev/react-turnstile
Before using Turnstile, you need a site key from Cloudflare:
0x or 1x for testing)Note: Site keys are NOT secret. They are safe to include in client-side code.
import { Turnstile } from '@marsidev/react-turnstile'
export default function LoginForm() {
return (
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
{/* Basic Turnstile widget */}
<Turnstile siteKey="YOUR_SITE_KEY" />
<button type="submit">Login</button>
</form>
)
}
Choose the size that fits your layout:
| Size | Dimensions | Best For |
|---|---|---|
normal (default) | 300×65px | Most forms, standard layouts |
compact | 150×140px | Mobile, tight spaces |
flexible | 100% width × 65px | Responsive layouts |
invisible | 0×0px | Hidden widgets (requires Invisible widget type from Cloudflare) |
// Compact size for mobile
<Turnstile
siteKey="YOUR_SITE_KEY"
options={{ size: 'compact' }}
/>
// Flexible width
<Turnstile
siteKey="YOUR_SITE_KEY"
options={{ size: 'flexible' }}
/>
Use the onSuccess callback to receive the verification token:
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'
export default function LoginForm() {
const [token, setToken] = useState<string | null>(null)
return (
<form>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<Turnstile
siteKey="YOUR_SITE_KEY"
onSuccess={(token) => setToken(token)}
/>
<button type="submit" disabled={!token}>
Login
</button>
</form>
)
}
'use client' // For Next.js App Router
import { Turnstile } from '@marsidev/react-turnstile'
import { useState } from 'react'
export default function ContactForm() {
const [token, setToken] = useState<string | null>(null)
const [isSubmitting, setIsSubmitting] = useState(false)
async function handleSubmit(e: React.FormEvent) {
e.preventDefault()
if (!token) return
setIsSubmitting(true)
// Send token to your server for validation
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, /* other form data */ })
})
if (response.ok) {
// Handle success
}
setIsSubmitting(false)
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" placeholder="Name" required />
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<Turnstile
siteKey="YOUR_SITE_KEY"
onSuccess={setToken}
/>
<button type="submit" disabled={!token || isSubmitting}>
{isSubmitting ? 'Sending...' : 'Send Message'}
</button>
</form>
)
}
The invisible size is only for the Invisible widget type from Cloudflare. Using it with a normal widget shows nothing.
Wrong:
// Shows nothing if widget type is not "Invisible"
<Turnstile siteKey="xxx" options={{ size: 'invisible' }} />
Correct:
// Use visible sizes for normal widgets
<Turnstile siteKey="xxx" options={{ size: 'normal' }} />
<Turnstile siteKey="xxx" options={{ size: 'compact' }} />
<Turnstile siteKey="xxx" options={{ size: 'flexible' }} />
Use Cloudflare's test keys that always pass validation:
1x00000000000000000000AA1x0000000000000000000000000000000AASee: https://developers.cloudflare.com/turnstile/troubleshooting/testing/